Я пытаюсь реализовать 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();
}
}
};
вопросы вот в чем:
- как использовать прослушиватель, чтобы иметь возможность «подключиться» к привязке данных и показать статус сообщения в TextView Fragment UI? где я должен разместить слушателя? в модели View или в View?
healthDataService
иHealthDataStore
должны сохраниться, когда приложение перейдет к другому действию, чтобы иметь возможность использовать для сбора данных из Samsung Health.
- Это что-то интересное, если никто не даст хорошего ответа, я попытаюсь провести несколько тестов, чтобы увидеть, лучше ли один способ, чем другой. Кстати, я не могу обещать вам, когда
здесь, возможно, нет правильного или неправильного ответа, но, в конце концов, я ищу правильный подход к этому...
Благодарность!
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);
}
}
Но у меня есть проблема с этим подходом, потому что он продолжает бросать меня:
Я не понимаю, почему это происходит и как это решить...
Вид: это пользовательский интерфейс (активность, фрагмент и т. д.)