Google MVP в картах

355
14 января 2018, 04:58

Я хочу использовать 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() + "");
                });
    }
}
Answer 1

ну тут всё просто, всё что касается карты должно иниться только после того как отработает 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 из примеров гугла, что я кинул выше

READ ALSO
Как правильно подключить Java SDK от vk?

Как правильно подключить Java SDK от vk?

Извиняюсь за глупый вопрос но уже довольно долго сижу над элементарной вещьюДля работы с VK API решил использовать их SDK

344
Множественный оператор ||

Множественный оператор ||

Интересует, как лучше отрефакторить такой фрагмент кода :

275