Я хочу использовать 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 из примеров гугла, что я кинул выше
Сборка персонального компьютера от Artline: умный выбор для современных пользователей