From 27cffd80cb335beb8aac8bacbfeba054344a5ef1 Mon Sep 17 00:00:00 2001 From: Joshua Liu Date: Tue, 24 Apr 2018 12:11:09 -0700 Subject: [PATCH] Navigator as ViewModel pattern --- .idea/caches/build_file_checksums.ser | Bin 736 -> 736 bytes .idea/codeStyles/Project.xml | 1 + app/build.gradle | 1 + .../research/app/MainActivity.java | 2 +- .../research/data/FakeTaskRepository.java | 13 +- .../domain/repository/TaskRepository.java | 7 ++ .../ConstantNextStepStrategyTest.java | 2 +- mobile-ui/build.gradle | 1 + .../research/mobile_ui/mapper/TaskMapper.java | 5 + .../perform_task/PerformTaskFragment.java | 26 ++-- presentation/build.gradle | 6 + .../presentation/mapper/TaskMapper.java | 7 ++ .../perform_task/PerformTaskViewModel.java | 117 +++++++----------- .../PerformTaskViewModelFactory.java | 16 +-- 14 files changed, 106 insertions(+), 98 deletions(-) diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser index ca50f46888e0a822020cbbed773a9048fd260404..b902e1e49c2f0fb078e1ec11557992187745a10e 100644 GIT binary patch delta 78 zcmV-U0I~nz1>gmcm;~foRbG*tcMwQN2LOj8$G&G3ABFHm5T%nN0VWXfbSvGkF^04v kK^@sorCT>FnAMgRZ+ delta 78 zcmV-U0I~nz1>gmcm;~Th{t1zscM$J3^7=?AA`v=V(d~#^0-KW~0VWV_Y&^zLY*qZ{ kE|gM6QP>}|SpgdX5O$~|lrMA|lVVuVy&}n#s*{8QTs?Ln6#xJL diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 5ef687212..2674959b1 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -608,6 +608,7 @@ BY_NAME +
diff --git a/app/build.gradle b/app/build.gradle index 359402093..437ca1cf0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -84,6 +84,7 @@ dependencies { implementation 'com.ryanharter.auto.value:auto-value-parcel-adapter:0.2.6' implementation "android.arch.lifecycle:extensions:1.1.1" + annotationProcessor "android.arch.lifecycle:compiler:1.1.1" implementation 'com.android.support:appcompat-v7:27.1.1' diff --git a/app/src/main/java/org/sagebionetworks/research/app/MainActivity.java b/app/src/main/java/org/sagebionetworks/research/app/MainActivity.java index eb0fe8474..8b598016d 100644 --- a/app/src/main/java/org/sagebionetworks/research/app/MainActivity.java +++ b/app/src/main/java/org/sagebionetworks/research/app/MainActivity.java @@ -57,7 +57,7 @@ protected void onCreate(Bundle savedInstanceState) { .build(); } if (performTaskFragment == null) { - performTaskFragment = PerformTaskFragment.newInstance(taskView); + performTaskFragment = PerformTaskFragment.newInstance("taskId"); getSupportFragmentManager() .beginTransaction() diff --git a/data/src/main/java/org/sagebionetworks/research/data/FakeTaskRepository.java b/data/src/main/java/org/sagebionetworks/research/data/FakeTaskRepository.java index 7b965fa8b..dd0b175be 100644 --- a/data/src/main/java/org/sagebionetworks/research/data/FakeTaskRepository.java +++ b/data/src/main/java/org/sagebionetworks/research/data/FakeTaskRepository.java @@ -38,6 +38,7 @@ import org.sagebionetworks.research.data.model.step.ConcreteUIStep; import org.sagebionetworks.research.domain.Schema; import org.sagebionetworks.research.domain.repository.TaskRepository; +import org.sagebionetworks.research.domain.result.TaskResult; import org.sagebionetworks.research.domain.step.Step; import org.sagebionetworks.research.domain.step.ui.UIStep; import org.sagebionetworks.research.domain.task.Task; @@ -47,14 +48,12 @@ import java.util.List; import java.util.UUID; +import io.reactivex.Completable; import io.reactivex.Single; import javax.inject.Inject; public class FakeTaskRepository implements TaskRepository { - @Inject - public FakeTaskRepository() { - } - + @Inject public FakeTaskRepository() {} @Override public Single getTask(final String taskIdentifier) { return Single.just(new Task() { @@ -106,6 +105,12 @@ public Single> getTaskSteps(final Task task) { )); } + @NonNull + @Override + public Completable setTaskResult(final TaskResult taskResult) { + return Completable.error(new UnsupportedOperationException("Not implemented yet")); + } + private UIStep createUIStep(String id) { return ConcreteUIStep.builder() .setIdentifier(id) diff --git a/domain/src/main/java/org/sagebionetworks/research/domain/repository/TaskRepository.java b/domain/src/main/java/org/sagebionetworks/research/domain/repository/TaskRepository.java index 3f2918bd8..d88d6c337 100644 --- a/domain/src/main/java/org/sagebionetworks/research/domain/repository/TaskRepository.java +++ b/domain/src/main/java/org/sagebionetworks/research/domain/repository/TaskRepository.java @@ -32,13 +32,16 @@ package org.sagebionetworks.research.domain.repository; +import android.support.annotation.CheckResult; import android.support.annotation.NonNull; +import org.sagebionetworks.research.domain.result.TaskResult; import org.sagebionetworks.research.domain.step.Step; import org.sagebionetworks.research.domain.task.Task; import java.util.List; +import io.reactivex.Completable; import io.reactivex.Single; public interface TaskRepository { @@ -47,4 +50,8 @@ public interface TaskRepository { @NonNull Single> getTaskSteps(Task task); + + @NonNull + @CheckResult + Completable setTaskResult(TaskResult taskResult); } diff --git a/domain/src/test/java/org/sagebionetworks/research/domain/task/navigation/strategy/next_step/ConstantNextStepStrategyTest.java b/domain/src/test/java/org/sagebionetworks/research/domain/task/navigation/strategy/next_step/ConstantNextStepStrategyTest.java index 6aba20ba0..5372e8306 100644 --- a/domain/src/test/java/org/sagebionetworks/research/domain/task/navigation/strategy/next_step/ConstantNextStepStrategyTest.java +++ b/domain/src/test/java/org/sagebionetworks/research/domain/task/navigation/strategy/next_step/ConstantNextStepStrategyTest.java @@ -37,7 +37,7 @@ import org.sagebionetworks.research.domain.task.navigation.strategy.StepNavigationStrategy.NextStepStrategy; import static org.junit.Assert.*; -import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.*; public class ConstantNextStepStrategyTest { diff --git a/mobile-ui/build.gradle b/mobile-ui/build.gradle index 8e7045317..9c354f2a6 100644 --- a/mobile-ui/build.gradle +++ b/mobile-ui/build.gradle @@ -91,6 +91,7 @@ dependencies { implementation 'android.arch.lifecycle:extensions:1.1.1' annotationProcessor "android.arch.lifecycle:compiler:1.1.1" testImplementation 'junit:junit:4.12' + testImplementation "android.arch.core:core-testing:1.1.1" androidTestImplementation 'com.android.support.test:runner:1.0.1' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' } diff --git a/mobile-ui/src/main/java/org/sagebionetworks/research/mobile_ui/mapper/TaskMapper.java b/mobile-ui/src/main/java/org/sagebionetworks/research/mobile_ui/mapper/TaskMapper.java index d4edf8f54..09759befb 100644 --- a/mobile-ui/src/main/java/org/sagebionetworks/research/mobile_ui/mapper/TaskMapper.java +++ b/mobile-ui/src/main/java/org/sagebionetworks/research/mobile_ui/mapper/TaskMapper.java @@ -32,5 +32,10 @@ package org.sagebionetworks.research.mobile_ui.mapper; +import javax.inject.Inject; + public class TaskMapper { + @Inject + public TaskMapper() { + } } diff --git a/mobile-ui/src/main/java/org/sagebionetworks/research/mobile_ui/perform_task/PerformTaskFragment.java b/mobile-ui/src/main/java/org/sagebionetworks/research/mobile_ui/perform_task/PerformTaskFragment.java index 58520e3b8..620379eec 100644 --- a/mobile-ui/src/main/java/org/sagebionetworks/research/mobile_ui/perform_task/PerformTaskFragment.java +++ b/mobile-ui/src/main/java/org/sagebionetworks/research/mobile_ui/perform_task/PerformTaskFragment.java @@ -43,15 +43,15 @@ import android.view.View; import android.view.ViewGroup; +import com.google.common.base.Strings; + import org.sagebionetworks.research.domain.mobile_ui.R; import org.sagebionetworks.research.mobile_ui.mapper.StepMapper; -import org.sagebionetworks.research.mobile_ui.show_step.StepPresenter; import org.sagebionetworks.research.mobile_ui.show_step.StepPresenterFactory; import org.sagebionetworks.research.mobile_ui.show_step.view.ShowStepFragment; import org.sagebionetworks.research.mobile_ui.show_step.view.ShowStepFragmentBase; import org.sagebionetworks.research.presentation.model.StepView; import org.sagebionetworks.research.presentation.model.StepView.NavDirection; -import org.sagebionetworks.research.presentation.model.TaskView; import org.sagebionetworks.research.presentation.perform_task.PerformTaskViewModel; import org.sagebionetworks.research.presentation.perform_task.PerformTaskViewModelFactory; @@ -70,7 +70,7 @@ * A placeholder fragment containing a simple view. */ public class PerformTaskFragment extends Fragment implements HasSupportFragmentInjector { - private static final String ARGUMENT_TASK_VIEW = "TASK_VIEW"; + private static final String ARGUMENT_TASK_IDENTIFIER = "TASK_VIEW"; @Inject DispatchingAndroidInjector fragmentDispatchingAndroidInjector; @@ -88,17 +88,15 @@ public class PerformTaskFragment extends Fragment implements HasSupportFragmentI private PerformTaskViewModel performTaskViewModel; - private StepPresenter stepPresenter; - - private TaskView taskView; + private String taskIdentifier; private Unbinder unbinder; - public static PerformTaskFragment newInstance(@NonNull TaskView taskView) { - checkNotNull(taskView); + public static PerformTaskFragment newInstance(@NonNull String taskIdentifier) { + checkNotNull(taskIdentifier); Bundle arguments = new Bundle(); - arguments.putParcelable(ARGUMENT_TASK_VIEW, taskView); + arguments.putString(ARGUMENT_TASK_IDENTIFIER, taskIdentifier); PerformTaskFragment fragment = new PerformTaskFragment(); fragment.setArguments(arguments); @@ -119,14 +117,14 @@ public void onCreate(Bundle savedInstanceState) { if (savedInstanceState == null) { Bundle arguments = getArguments(); if (arguments != null) { - taskView = getArguments().getParcelable(ARGUMENT_TASK_VIEW); + taskIdentifier = getArguments().getString(ARGUMENT_TASK_IDENTIFIER); } } else { - taskView = savedInstanceState.getParcelable(ARGUMENT_TASK_VIEW); + taskIdentifier = savedInstanceState.getString(ARGUMENT_TASK_IDENTIFIER); } - checkState(taskView != null, "no taskView found"); + checkState(!Strings.isNullOrEmpty(taskIdentifier), "taskIdentifier cannot be null or empty"); - performTaskViewModel = ViewModelProviders.of(this, taskViewModelFactory.create(taskView)) + performTaskViewModel = ViewModelProviders.of(this, taskViewModelFactory.create(taskIdentifier)) .get(PerformTaskViewModel.class); performTaskViewModel.getStep().observe(this, this::showStep); @@ -143,7 +141,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, @Override public void onSaveInstanceState(Bundle outState) { if (outState != null) { - outState.putParcelable(ARGUMENT_TASK_VIEW, taskView); + outState.putString(ARGUMENT_TASK_IDENTIFIER, taskIdentifier); } } diff --git a/presentation/build.gradle b/presentation/build.gradle index 14f979af4..37bf43af6 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -69,6 +69,8 @@ dependencies { implementation 'com.google.guava:guava:24.1-android' implementation "android.arch.lifecycle:extensions:1.1.1" + annotationProcessor "android.arch.lifecycle:compiler:1.1.1" + implementation 'org.slf4j:slf4j-api:1.7.21' api 'com.google.dagger:dagger-android:2.15' @@ -83,7 +85,11 @@ dependencies { annotationProcessor 'com.ryanharter.auto.value:auto-value-parcel:0.2.6' implementation 'com.android.support:appcompat-v7:27.1.1' + testImplementation 'junit:junit:4.12' + testImplementation "android.arch.core:core-testing:1.1.1" + testImplementation 'org.mockito:mockito-core:2.8.9' + androidTestImplementation 'com.android.support.test:runner:1.0.1' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' } diff --git a/presentation/src/main/java/org/sagebionetworks/research/presentation/mapper/TaskMapper.java b/presentation/src/main/java/org/sagebionetworks/research/presentation/mapper/TaskMapper.java index 07cdb6a95..86d6edbc0 100644 --- a/presentation/src/main/java/org/sagebionetworks/research/presentation/mapper/TaskMapper.java +++ b/presentation/src/main/java/org/sagebionetworks/research/presentation/mapper/TaskMapper.java @@ -40,10 +40,17 @@ import org.sagebionetworks.research.domain.task.Task; import org.sagebionetworks.research.presentation.model.TaskView; +import javax.inject.Inject; + /** * Map a {@link Task} to a {@link TaskView} when data is moving between the Domain layer and this layer. */ public class TaskMapper implements Function { + @Inject + public TaskMapper() { + + } + @Override @NonNull public TaskView apply(@NonNull Task input) { diff --git a/presentation/src/main/java/org/sagebionetworks/research/presentation/perform_task/PerformTaskViewModel.java b/presentation/src/main/java/org/sagebionetworks/research/presentation/perform_task/PerformTaskViewModel.java index 630905903..d40a89847 100644 --- a/presentation/src/main/java/org/sagebionetworks/research/presentation/perform_task/PerformTaskViewModel.java +++ b/presentation/src/main/java/org/sagebionetworks/research/presentation/perform_task/PerformTaskViewModel.java @@ -34,10 +34,10 @@ import android.arch.lifecycle.LiveData; import android.arch.lifecycle.MutableLiveData; -import android.arch.lifecycle.Transformations; import android.arch.lifecycle.ViewModel; import android.support.annotation.MainThread; import android.support.annotation.NonNull; +import android.support.annotation.VisibleForTesting; import org.sagebionetworks.research.domain.presentation.model.LoadableResource; import org.sagebionetworks.research.domain.repository.TaskRepository; @@ -45,10 +45,9 @@ import org.sagebionetworks.research.domain.result.TaskResult; import org.sagebionetworks.research.domain.step.Step; import org.sagebionetworks.research.domain.task.Task; -import org.sagebionetworks.research.domain.task.Task.Progress; import org.sagebionetworks.research.domain.task.navigation.StepNavigator; import org.sagebionetworks.research.domain.task.navigation.StepNavigatorFactory; -import org.sagebionetworks.research.presentation.mapper.StepMapper; +import org.sagebionetworks.research.presentation.mapper.TaskMapper; import org.sagebionetworks.research.presentation.model.StepView; import org.sagebionetworks.research.presentation.model.StepView.NavDirection; import org.sagebionetworks.research.presentation.model.TaskView; @@ -56,116 +55,76 @@ import org.slf4j.LoggerFactory; import org.threeten.bp.Instant; +import java.util.List; import java.util.UUID; +import io.reactivex.disposables.CompositeDisposable; + import static com.google.common.base.Preconditions.checkNotNull; @MainThread public class PerformTaskViewModel extends ViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(PerformTaskViewModel.class); - private final MutableLiveData currentStepLiveData; - - private final LiveData nextStepLiveData; - - private final LiveData previousStepLiveData; + private final CompositeDisposable compositeDisposable; - private final LiveData progressLiveData; - - private StepNavigator stepNavigator = new StepNavigator() { - @Override - public Step getStep(final String identifier) { - return null; - } - - @Override - public Step getNextStep(final Step step, final TaskResult taskResult) { - return new Step() { - @NonNull - @Override - public String getIdentifier() { - return UUID.randomUUID().toString(); - } - - @NonNull - @Override - public String getType() { - return "step"; - } - }; - } - - @Override - public Step getPreviousStep(final Step step, final TaskResult taskResult) { - return new Step() { - @NonNull - @Override - public String getIdentifier() { - return UUID.randomUUID().toString(); - } - - @NonNull - @Override - public String getType() { - return "step"; - } - }; - } + private final MutableLiveData currentStepLiveData; - @Override - public Progress getProgress(final Step step, final TaskResult taskResult) { - return null; - } - }; + private StepNavigator stepNavigator; private final StepNavigatorFactory stepNavigatorFactory; private final MutableLiveData stepViewLiveData; + private final String taskIdentifier; + private final MutableLiveData> taskLiveData; + private final TaskMapper taskMapper; + + private final TaskRepository taskRepository; + private final TaskResult.Builder taskResultBuilder; private final MutableLiveData taskResultLiveData; - private final TaskView taskView; + private TaskView taskView; - - public PerformTaskViewModel(@NonNull TaskView taskView, @NonNull StepNavigatorFactory stepNavigatorFactory, - @NonNull TaskRepository taskRepository, @NonNull StepMapper stepMapper) { - this.taskView = checkNotNull(taskView); + public PerformTaskViewModel(@NonNull String taskIdentifier, @NonNull StepNavigatorFactory stepNavigatorFactory, + @NonNull TaskRepository taskRepository, @NonNull TaskMapper taskMapper) { + this.taskIdentifier = checkNotNull(taskIdentifier); this.stepNavigatorFactory = checkNotNull(stepNavigatorFactory); + this.taskRepository = checkNotNull(taskRepository); + this.taskMapper = checkNotNull(taskMapper); taskLiveData = new MutableLiveData<>(); taskResultLiveData = new MutableLiveData<>(); -// currentStepLiveData = new MutableLiveData<>(); taskResultBuilder = new TaskResult.Builder("id", UUID.randomUUID()); taskResultBuilder.setStartTime(Instant.now()); + initTaskSteps(); + taskResultLiveData.setValue(taskResultBuilder.build()); currentStepLiveData = new MutableLiveData<>(); currentStepLiveData.setValue(null); stepViewLiveData = new MutableLiveData<>(); - - nextStepLiveData = Transformations.switchMap(currentStepLiveData, (Step s) -> - Transformations.map(taskResultLiveData, (TaskResult tr) -> stepNavigator.getNextStep(s, tr)) - ); - previousStepLiveData = Transformations.switchMap(currentStepLiveData, (Step s) -> - Transformations.map(taskResultLiveData, (TaskResult tr) -> stepNavigator.getPreviousStep(s, tr)) - ); - progressLiveData = Transformations.switchMap(currentStepLiveData, (Step s) -> - Transformations.map(taskResultLiveData, (TaskResult tr) -> stepNavigator.getProgress(s, tr)) - ); + compositeDisposable = new CompositeDisposable(); } public void addAsyncResult(Result result) { + if (LOGGER.isDebugEnabled()){ + LOGGER.debug("addAsyncResult called with result: {}", result); + } taskResultBuilder.addAsyncResult(result); taskResultLiveData.setValue(taskResultBuilder.build()); } public void addStepResult(Result result) { + if (LOGGER.isDebugEnabled()){ + LOGGER.debug("addStepResult called with result: {}", result); + } taskResultBuilder.addStepResult(result); taskResultLiveData.setValue(taskResultBuilder.build()); } @@ -194,6 +153,7 @@ public void goBack() { LOGGER.debug("goBack called"); Step backStep = stepNavigator.getPreviousStep(currentStepLiveData.getValue(), taskResultBuilder.build()); + currentStepLiveData.setValue(backStep); stepViewLiveData.setValue( StepView.builder() .setIdentifier(backStep.getIdentifier()) @@ -206,6 +166,7 @@ public void goForward() { LOGGER.debug("goForward called"); Step forwardStep = stepNavigator.getNextStep(currentStepLiveData.getValue(), taskResultBuilder.build()); + currentStepLiveData.setValue(forwardStep); stepViewLiveData.setValue( StepView.builder() .setIdentifier(forwardStep.getIdentifier()) @@ -213,4 +174,20 @@ public void goForward() { .build() ); } + + + + @VisibleForTesting + void handleTaskSteps(List steps, Throwable t) { + + } + + void initTaskSteps() { + compositeDisposable.add(taskRepository.getTask(taskIdentifier) + .doOnSuccess(task -> { + taskView = taskMapper.apply(task); + }) + .flatMap(taskRepository::getTaskSteps) + .subscribe(this::handleTaskSteps)); + } } diff --git a/presentation/src/main/java/org/sagebionetworks/research/presentation/perform_task/PerformTaskViewModelFactory.java b/presentation/src/main/java/org/sagebionetworks/research/presentation/perform_task/PerformTaskViewModelFactory.java index c6ecf5d50..dfc83ab7f 100644 --- a/presentation/src/main/java/org/sagebionetworks/research/presentation/perform_task/PerformTaskViewModelFactory.java +++ b/presentation/src/main/java/org/sagebionetworks/research/presentation/perform_task/PerformTaskViewModelFactory.java @@ -38,30 +38,29 @@ import org.sagebionetworks.research.domain.repository.TaskRepository; import org.sagebionetworks.research.domain.task.navigation.StepNavigatorFactory; -import org.sagebionetworks.research.presentation.mapper.StepMapper; -import org.sagebionetworks.research.presentation.model.TaskView; +import org.sagebionetworks.research.presentation.mapper.TaskMapper; import javax.inject.Inject; import static com.google.common.base.Preconditions.checkNotNull; public class PerformTaskViewModelFactory { - private final StepMapper stepMapper; + private final TaskMapper taskMapper; private final StepNavigatorFactory stepNavigatorFactory; private final TaskRepository taskRepository; @Inject - public PerformTaskViewModelFactory(StepNavigatorFactory stepNavigatorFactory, StepMapper stepMapper, + public PerformTaskViewModelFactory(StepNavigatorFactory stepNavigatorFactory, TaskMapper taskMapper, final TaskRepository taskRepository) { this.stepNavigatorFactory = stepNavigatorFactory; - this.stepMapper = stepMapper; + this.taskMapper = taskMapper; this.taskRepository = taskRepository; } - public ViewModelProvider.Factory create(@NonNull TaskView taskView) { - checkNotNull(taskView); + public ViewModelProvider.Factory create(@NonNull String taskIdentifier) { + checkNotNull(taskIdentifier); return new ViewModelProvider.Factory() { @NonNull @@ -70,7 +69,8 @@ public ViewModelProvider.Factory create(@NonNull TaskView taskView) { public T create(@NonNull Class modelClass) { if (modelClass.isAssignableFrom(PerformTaskViewModel.class)) { // noinspection unchecked - return (T) new PerformTaskViewModel(taskView, stepNavigatorFactory, taskRepository, stepMapper); + return (T) new PerformTaskViewModel(taskIdentifier, stepNavigatorFactory, taskRepository, + taskMapper); } throw new IllegalArgumentException("Unknown ViewModel class"); }