Архитектура MVVM со сторонним API с прослушивателем

Я пытаюсь реализовать MVVM с привязкой данных + Samsung Health SDK, я немного знаком с концепцией MVVM:

  • Модель просмотра: это мост между вашим представлением и вашей моделью.
  • Модель: это может быть ваш источник данных (БД, сетевой источник и т. д.)
  • где я должен разместить или выставить объекты healthDataService и HealthDataStore? в классе приложений? ViewModel? или Модель?

поэтому, если у меня есть фрагмент:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable name="isLoading" type="local.team.dev.shealth.mvvm.SplashViewModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/cardview_light_background"
        android:orientation="vertical">

        <TextView
            android:id="@+id/loading_tv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center_vertical|center_horizontal"
            android:text="@string/loading_splash"
            android:textAlignment="center"
            app:visibleGone="@{isLoading}"/>
    </LinearLayout>
</layout>

и ниже работает Samsung Health SDK:

у него есть объект службы и объект хранилища данных, как показано ниже:

// where should i put this 2 objects in MVVM architecture?
HealthDataService healthDataService = new HealthDataService();
    try {
        healthDataService.initialize(this);
    } catch (Exception e) {
        e.printStackTrace();
    }

    // Create a HealthDataStore instance and set its listener
    mStore = new HealthDataStore(this, mConnectionListener);
    // Request the connection to the health data store
    mStore.connectService();

который mStore использует прослушиватель следующим образом, чтобы определить статус подключения:

// where should i put this listener in MVVM architecture?
// and how to bind it with databinding in View UI
private final HealthDataStore.ConnectionListener mConnectionListener = new HealthDataStore.ConnectionListener() {

    @Override
    public void onConnected() {
        Log.d(APP_TAG, "Health data service is connected.");
        mReporter = new StepCountReporter(mStore);
        if (isPermissionAcquired()) {
            // if it is connected and permission acquired then go to next activity
        } else {
            requestPermission(); // <-- UI trigger data permission here
        }
    }

    @Override
    public void onConnectionFailed(HealthConnectionErrorResult error) {
        Log.d(APP_TAG, "Health data service is not available.");
        showConnectionFailureDialog(error);
        // Can i display message to fragment ui // Can i display message to fragment ui
        // using data binding if connection failed (and perhaps show retry button)
    }

    @Override
    public void onDisconnected() {
        Log.d(APP_TAG, "Health data service is disconnected.");
        // Can i display message to fragment ui
        // and tell the status with data binding
        if (!isFinishing()) {
            mStore.connectService();
        }
    }
};

вопросы вот в чем:

  1. как использовать прослушиватель, чтобы иметь возможность «подключиться» к привязке данных и показать статус сообщения в TextView Fragment UI? где я должен разместить слушателя? в модели View или в View?

healthDataService и HealthDataStore должны сохраниться, когда приложение перейдет к другому действию, чтобы иметь возможность использовать для сбора данных из Samsung Health.

  1. Это что-то интересное, если никто не даст хорошего ответа, я попытаюсь провести несколько тестов, чтобы увидеть, лучше ли один способ, чем другой. Кстати, я не могу обещать вам, когда

здесь, возможно, нет правильного или неправильного ответа, но, в конце концов, я ищу правильный подход к этому...

Благодарность!


EDIT: ниже показано, к чему я пытаюсь подойти, однако это дает мне нулевую ссылку на объект T_T

Остерегайтесь длинного содержания

Я публикую это для обсуждения моего подхода, но пока это не работает.

В общем, как я это делаю:

+--------+    +-------------+    +----------------+  +----------+  +-------+
|activity+----+ActivityClass|    |ApplicationClass+--+Repository+--+Service|
+--------+    +------+------+    +-----+----------+  +-----+----+  +-------+
                     |                 |                   |
+--------+    +------+------+    +-----+----+         +----+---+
|fragment+----+FragmentClass+----+View Model|         |Listener|
|dataBind|    |with DataBind|    |Class     |         +--------+
+--------+    +-------------+    +----------+

так вот мой класс приложения

class SHealth extends Application {
    public static final String APP_TAG = "SimpleHealth";
    private HealthDataStore mStore;

    public HealthDataService getHealthService() { return HealthService.getInstance(); }
    public HealthRepository getRepository() { return HealthRepository.getInstance(getHealthService(), this); }
}

и в моем классе Activity и Fragment у меня это так:

public class SplashActivity extends AppCompatActivity {
    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_splash);
        if (savedInstanceState == null) {
            SplashFragment fragment = new SplashFragment();
            getSupportFragmentManager().beginTransaction()
                .add(R.id.fragment_container, fragment, SplashFragment.TAG).commit();
        }
    }
}

// MY FRAGMENT CLASS
public class SplashFragment extends Fragment {

    public static final String TAG = "SplashViewModel";

    private SHealth app;
    private TextView loading_tv;

    private FragmentSplashBinding mBinding;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        mBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_splash, container, false);
        return mBinding.getRoot();
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        app = (SHealth) this.getActivity().getApplication();
        loading_tv = this.loading_tv;

        SplashViewModel.Factory factory = new SplashViewModel.Factory(app);

        // i'm not sure what's the diffrent using factory or not...
        final SplashViewModel viewModel = ViewModelProviders.of(this, factory).get(SplashViewModel.class);
        //final SplashViewModel viewModel = ViewModelProviders.of(this).get(SplashViewModel.class);

        subscribeUi(viewModel);
    }

    private void subscribeUi(SplashViewModel viewModel) {
        // Update the list when the data changes
        LiveData<HealthDSConnectionListener.Status> mObservableStatus = viewModel.getConnectionStatus();
        Observer<HealthDSConnectionListener.Status> observer = new Observer<HealthDSConnectionListener.Status>() {
            @Override
            public void onChanged(@Nullable HealthDSConnectionListener.Status status) {
                if (status == HealthDSConnectionListener.Status.CONNECTED) {
                    Log.v("statusOnChange", "Connected");
                    //mBinding.setIsLoading(true);
                    //mBinding.setConnectionStatus("Connected");
                } else if(status == HealthDSConnectionListener.Status.DISCONNECTED){
                    Log.v("statusOnChange", "DISCONNECTED");
                    //mBinding.setIsLoading(true);
                    //mBinding.setConnectionStatus("DISCONNECTED");
                }else{
                    Log.v("statusOnChange", "Failed");
                    //mBinding.setIsLoading(true);
                    //mBinding.setConnectionStatus("Failed");
                }
            }
        };

        mObservableStatus.observe(this,observer);
    }
}

и ниже мой класс ViewModel:

public class SplashViewModel extends AndroidViewModel {

    private final MediatorLiveData<HealthDSConnectionListener.Status> mObservableStatus;
    private final HealthDSConnectionListener mConnectionListener;

    public SplashViewModel(@NonNull Application application) {
        super(application);
        HealthRepository repository = ((SHealth) application).getRepository();

        mObservableStatus = new MediatorLiveData<>();
        mObservableStatus.setValue(null);

        mConnectionListener = new HealthDSConnectionListener(repository){
            @Override
            public void onConnected() {
                super.onConnected();
                Log.v("statusOnChange", "C");
            }

            @Override
            public void onConnectionFailed(HealthConnectionErrorResult error) {
                super.onConnectionFailed(error);
                Log.v("statusOnChange", "F");
            }

            @Override
            public void onDisconnected() {
                super.onDisconnected();
                Log.v("statusOnChange", "D");
            }
        };

        repository.connectDataStore(mConnectionListener);
        LiveData<HealthDSConnectionListener.Status> status = repository.getConnectionStatus();
        mObservableStatus.addSource(status, mObservableStatus::setValue);

    }

    public LiveData<HealthDSConnectionListener.Status> getConnectionStatus(){ return mObservableStatus; }

    public static class Factory extends ViewModelProvider.NewInstanceFactory {

        @NonNull
        private final Application mApplication;
        private final HealthRepository mRepository;

        public Factory(@NonNull Application application) {
            mApplication = application;
            mRepository = ((SHealth) application).getRepository();
        }

        @Override
        public <T extends ViewModel> T create(Class<T> modelClass) {
            //noinspection unchecked
            return (T) new SplashViewModel(mApplication);
        }
    }
}

и ниже мой класс репозитория:

public class HealthRepository {
    private static HealthRepository sHealthInstance;
    private final HealthDataService mService;
    private HealthDataStore mStore;
    private SHealth app;

    private MutableLiveData<HealthDSConnectionListener.Status> DSConnectionStatus;
    public MutableLiveData<HealthDSConnectionListener.Status> getDSConnectionStatus(){
        if (DSConnectionStatus == null) { DSConnectionStatus = new MutableLiveData<HealthDSConnectionListener.Status>(); }
        return DSConnectionStatus;
    }
    public void setDSConnectionStatus(HealthDSConnectionListener.Status status){ getDSConnectionStatus().setValue(status); }
    private MediatorLiveData<HealthDSConnectionListener.Status> mObservableConnectionStatus;

    private HealthRepository(final HealthDataService service, final SHealth context) {
        this.app = context;
        mService = service;

        try { mService.initialize(app); }
        catch (Exception e) { e.printStackTrace(); }

        mObservableConnectionStatus = new MediatorLiveData<>();
        mObservableConnectionStatus.setValue(null);
        mObservableConnectionStatus.addSource(DSConnectionStatus, mObservableConnectionStatus::setValue);
    }

    public void connectDataStore(HealthDSConnectionListener listener) {
        if(mStore == null) {
            mStore = new HealthDataStore(app, listener);
            mStore.connectService();
        }
    }

    public static HealthRepository getInstance(final HealthDataService service, final Context context) {
        if (sHealthInstance == null) {
            synchronized (HealthRepository.class) {
                if (sHealthInstance == null) {
                    sHealthInstance = new HealthRepository(service, (SHealth) context);
                }
            }
        }
        return sHealthInstance;
    }

    public LiveData<HealthDSConnectionListener.Status> getConnectionStatus() { return mObservableConnectionStatus; }
}

и вот мой класс обслуживания:

public abstract class HealthService {

    private static HealthDataService sInstance;
    public static HealthDataService getInstance() {
        if (sInstance == null) {
            synchronized (HealthService.class) {
                if (sInstance == null) {
                    sInstance = new HealthDataService();
                    return sInstance;
                }
            }
        }
        return sInstance;
    }
}

с моим «внутренним» прослушивателем, расширяющимся от прослушивателя API по умолчанию:

причину, по которой я расширяю прослушиватель по умолчанию, можно найти здесь

public class HealthDSConnectionListener implements HealthDataStore.ConnectionListener {

    public enum Status{
        CONNECTED(1), DISCONNECTED(2), FAILED(0);
        private final int index;
        Status(int index) { this.index = index; }
        int getIndex() { return this.index; }
    }

    private final HealthRepository repo;
    public HealthDSConnectionListener(HealthRepository repo) { this.repo = repo; }

    @Override public void onConnected() {
        repo.setDSConnectionStatus(Status.CONNECTED);
    }
    @Override public void onDisconnected() {
        repo.setDSConnectionStatus(Status.DISCONNECTED);
    }
    @Override public void onConnectionFailed(HealthConnectionErrorResult healthConnectionErrorResult) {
        repo.setDSConnectionStatus(Status.FAILED);
    }
}

Но у меня есть проблема с этим подходом, потому что он продолжает бросать меня:

Я не понимаю, почему это происходит и как это решить...

Вид: это пользовательский интерфейс (активность, фрагмент и т. д.)


person AnD    schedule 06.12.2017    source источник
comment
Спасибо @MatPag, да, я все еще борюсь с этим, я могу опубликовать свой подход позже, но пока не решил...   -  person MatPag    schedule 09.12.2017
comment
НЕИСПРАВНОЕ ИСКЛЮЧЕНИЕ: основной процесс: local.team.dev.shealthdemo, PID: 3458 java.lang.RuntimeException: невозможно запустить активность ComponentInfo{local.team.dev.shealthdemo/local.team.dev.shealthdemo.SplashActivity}: java. lang.NullPointerException: попытка вызвать виртуальный метод «void android.arch.lifecycle.LiveData.observeForever(android.arch.lifecycle.Observer)» для ссылки на нулевой объект в android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2416) в android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476) в android.app.ActivityThread.-wrap11(ActivityThread.java) в android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344) в android.os .Handler.dispatchMessage(Handler.java:102) в android.os.Looper.loop(Looper.java:148) в android.app.Activi tyThread.main(ActivityThread.java:5417) в java.lang.reflect.Method.invoke(собственный метод) в com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) в com.android. internal.os.ZygoteInit.main(ZygoteInit.java:616) Причина: java.lang.NullPointerException: попытка вызвать виртуальный метод «void android.arch.lifecycle.LiveData.observeForever(android.arch.lifecycle.Observer)» в нулевая ссылка на объект в android.arch.lifecycle.MediatorLiveData$Source.plug(MediatorLiveData.java:141) в android.arch.lifecycle.MediatorLiveData.onActive(MediatorLiveData.java:118) в android.arch.lifecycle.LiveData$LifecycleBoundObserver .activeStateChanged(LiveData.java:389) в android.arch.lifecycle.LiveData$LifecycleBoundObserver.onStateChanged(LiveData.java:378) в android. arch.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:353) в android.arch.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.java:180) в android.arch.lifecycle.LiveData.observe(LiveData.java:201) на android.arch.lifecycle.LiveData.observeForever(LiveData.java:220) на android.arch.lifecycle.MediatorLiveData$Source.plug(MediatorLiveData.java:141) на android.arch.lifecycle. MediatorLiveData.onActive(MediatorLiveData.java:118) на android.arch.lifecycle.LiveData$LifecycleBoundObserver.activeStateChanged(LiveData.java:389) на android.arch.lifecycle.LiveData$LifecycleBoundObserver.onStateChanged(LiveData.java:378) на android .arch.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:353) в android.arch.lifecycle.LifecycleRegistry.forwardPass(LifecycleRegistry.java:291) в android.arch.lifecycle.LifecycleRegistry.sync(LifecycleRegistry.java:331) ) в android.arch.lifecycle.LifecycleRegistry.moveToState(LifecycleRegistry.java:137) в android.arch.lifecycle.LifecycleRegistry.handleLifecycleEvent(LifecycleRegistry.java:123) в android.support.v4.app.Fragment.performStart(Fragment. java:2391) на android.support.v4.app.Fragmen tManagerImpl.moveToState(FragmentManager.java:1458) в android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1740) в android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1809) в android .support.v4.app.FragmentManagerImpl.dispatchStateChange(FragmentManager.java:3217) в android.support.v4.app.FragmentManagerImpl.dispatchStart(FragmentManager.java:3176) в android.support.v4.app.FragmentController.dispatchStart(FragmentController) .java:203) в android.support.v4.app.FragmentActivity.onStart(FragmentActivity.java:570) в android.support.v7.app.AppCompatActivity.onStart(AppCompatActivity.java:177) в android.app.Instrumentation. callActivityOnStart(Instrumentation.java:1237) в android.app.Activity.performStart(Activ ity.java:6253) в android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2379) в android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476)  в android.app.ActivityThread.-wrap11(ActivityThread.java) на android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344) на android.os.Handler.dispatchMessage(Handler.java:102) на android.os. Looper.loop(Looper.java:148) на android.app.ActivityThread.main(ActivityThread.java:5417) на java.lang.reflect.Method.invoke(собственный метод) на com.android.internal.os.ZygoteInit$ MethodAndArgsCaller.run(ZygoteInit.java:726) на com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)   -  person AnD    schedule 10.12.2017