Я хочу использовать MVP и карту. Для первого запуска работает все идеально, но когда я вращаю телефон получаю NullExeption
в addMarkersOnMap
потому что GoogleMap еще не создался.
Объясните пожалуйста логику, как правильно нужно работать с такими приложениями когда кроме Rx есть еще и CallBack. Я понимаю что мой код не правильный. Поэтому и ожидаю в свой адрес критику, но с объяснением. Заранее спасибо.
public class MainActivity extends MvpAppCompatActivity implements IMain, OnMapReadyCallback {
@InjectPresenter MainPresenter presenter;
@BindView(R.id.frameProgress) FrameLayout frameProgress;
private GoogleMap googleMap;
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
.findFragmentById(R.id.map);
mapFragment.getMapAsync(this);
}
@Override
public void showProgress() {
frameProgress.setVisibility(View.VISIBLE);
}
@Override
public void hideProgress() {
frameProgress.setVisibility(View.INVISIBLE);
}
@Override
public void onMapReady(GoogleMap googleMap) {
this.googleMap=googleMap;
}
@Override
public void addMarkersOnMap(Place place) {
for (Place.DataItem item: place.getData()) {
LatLng latLng=new LatLng(item.getLat(),item.getLng());
googleMap.addMarker(new MarkerOptions().position(latLng));
}
}
}
@InjectViewState
public class MainPresenter extends MvpPresenter<IMain> {
@Override
protected void onFirstViewAttach() {
super.onFirstViewAttach();
getViewState().showProgress();
ApiRequest.getPlace()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnTerminate(() -> getViewState().hideProgress())
.subscribe(place -> {
getViewState().addMarkersOnMap(place);
},throwable -> {
Log.e("MainPresenter=onFirstViewAttach", throwable.getMessage() + "");
});
}
}
ну тут всё просто, всё что касается карты должно иниться только после того как отработает onMapReady()
т.е. ваш запрос к апи желательно вызывать после коллбека. т.е. вы выносите запрос в отдельный метод и вызываете его из активити в коллбеке.
Будет так:
@Override
public void onMapReady(GoogleMap googleMap) {
this.googleMap=googleMap;
presenter.getPlace();
}
И потом в презентере (не забудьте в контракте метод объявить):
@Override
public void getPlace() {
getViewState().showProgress();
ApiRequest.getPlace()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnTerminate(() -> getViewState().hideProgress())
.subscribe(place -> {
getViewState().addMarkersOnMap(place);
},throwable -> {
Log.e("MainPresenter=onFirstViewAttach", throwable.getMessage() + "");
});
}
И рекомендации по использованию RxJava
:
getViewState().showProgress();
вызывайте непосредственно в цепочке Rx'a в методе doOnSubscribe();
и скрытие прогресса лучше в doAfterTerminate()
или doFinally()
Эти методы вызываются после того, как отработал subscribe()
как в случае успеха, так и в случае ошибки т.е. прогресс закроется после того как всё необходимое заатачится на вьюху или упадет ошибка.
И можно совсем довести до идеала - сам запрос к Domain
слою (гуглу) скрыть от презентера и вынести в Interactor
- класс прослойка между презентером и моделью, он берет данные из модели в "сыром" виде и отдает их прозентеру в нужном для него виде, презентер выполняет логические операции и вызывает вью. И если ещё красивее то используйте UseCase
. В Clean Architecture
это, если простыми словами, интерактор который отвечает только за один конкретный запрос к модели. К примеру вам от гугла надо два запроса. один даёт Place
другой даёт Polyline
. Это либо один Interactor
, либо два UseCase
. Ну если капнуть еще дальше - то работу с гуглом вам надо еще обернуть в паттерн Repository
Хорошие примеры всего этого : Google blue prints GitHub repo
And one more thing:
У вас в методе вьюхи addMarkersOnMap(Place place)
есть ненужная логика. проверки типов. доставание данных из объекта.
Помните, что ваша View
должна быть максимально "тупой", поэтому логику доставания LatLng
из Place
выполняйте в Presenter
'e а этот метод с маркером должен принимать уже готовые данные addMarkersOnMap(LatLng place)
после чего от просто сетит координату в MarkerOptions
и потом в карту. Вы можете возразить и сказать, почему бы Presenter
'у сразу не передавать MarkerOptions
. Ответ - MarkerOptions
отвечает за визуальное отображение на карте и является частью View
. К примеру вы месяц пишете на Google Maps
а потом вдруг решите перейти на другие OpenStreetMap
или Here
к примеру. Так, вам придется менять только слой View
. А если классы из пакета карты будут и в презентере, то вам и его придется переписывать. Хотя LatLng
тоже конечно лежит в пакете гугл карт. Если уж совсем упороться в мои советы, то тогда презентер работать должен с двумя long
переменными (latitude & longtitude)
но это уже слишком и сугубо личное дело каждого насколько сильно упарываться в MVP
) Поступайте как вам удобнее.
Главное - общие принципы:
1 - View
максимально тупая.
2 - Presenter
выполняет логические операции.
3 - Interactor/UseCases
достают презентеру данные из слоя данных
4 - Слой данных (сеть/локальная БД/ префы/ Хранилище телефона) Я пользуюсь паттерном Repository
из примеров гугла, что я кинул выше
Кофе для программистов: как напиток влияет на продуктивность кодеров?
Рекламные вывески: как привлечь внимание и увеличить продажи
Стратегії та тренди в SMM - Технології, що формують майбутнє сьогодні
Выделенный сервер, что это, для чего нужен и какие характеристики важны?
Извиняюсь за глупый вопрос но уже довольно долго сижу над элементарной вещьюДля работы с VK API решил использовать их SDK
У меня такое изображение