From 8c2d676bd73fc34b75b5bd2fdf4d2f772e84c6ff Mon Sep 17 00:00:00 2001 From: Daniel Novak Date: Mon, 2 May 2016 13:19:19 +0200 Subject: [PATCH 01/46] Make getViewModel as NonNull --- .../java/eu/inloop/viewmodel/ViewModelHelper.java | 12 +++++++++++- .../inloop/viewmodel/base/ViewModelBaseActivity.java | 5 ++++- .../inloop/viewmodel/base/ViewModelBaseFragment.java | 5 ++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/library/src/main/java/eu/inloop/viewmodel/ViewModelHelper.java b/library/src/main/java/eu/inloop/viewmodel/ViewModelHelper.java index 82c28fc..2cbad96 100644 --- a/library/src/main/java/eu/inloop/viewmodel/ViewModelHelper.java +++ b/library/src/main/java/eu/inloop/viewmodel/ViewModelHelper.java @@ -152,8 +152,18 @@ public void onStart() { } - @Nullable + /** + * Returns the current ViewModel instance associated with the Fragment or Activity. + * Throws an {@link IllegalStateException} in case the ViewModel is null. This can happen + * if you call this method too soon - before {@link Activity#onCreate(Bundle)} or {@link Fragment#onCreate(Bundle)} + * or this {@link ViewModelHelper} is not properly setup. + * @return {@link R} + */ + @NonNull public R getViewModel() { + if (null == mViewModel) { + throw new IllegalStateException("ViewModel is not ready. Are you calling this method before Activity/Fragment onCreate?"); //NON-NLS + } return mViewModel; } diff --git a/library/src/main/java/eu/inloop/viewmodel/base/ViewModelBaseActivity.java b/library/src/main/java/eu/inloop/viewmodel/base/ViewModelBaseActivity.java index d413e76..cc86e98 100644 --- a/library/src/main/java/eu/inloop/viewmodel/base/ViewModelBaseActivity.java +++ b/library/src/main/java/eu/inloop/viewmodel/base/ViewModelBaseActivity.java @@ -53,8 +53,11 @@ public void onDestroy() { super.onDestroy(); } + /** + * @see ViewModelHelper#getViewModel() + */ @SuppressWarnings("unused") - @Nullable + @NonNull public R getViewModel() { return mViewModeHelper.getViewModel(); } diff --git a/library/src/main/java/eu/inloop/viewmodel/base/ViewModelBaseFragment.java b/library/src/main/java/eu/inloop/viewmodel/base/ViewModelBaseFragment.java index 92f2265..a6ba060 100644 --- a/library/src/main/java/eu/inloop/viewmodel/base/ViewModelBaseFragment.java +++ b/library/src/main/java/eu/inloop/viewmodel/base/ViewModelBaseFragment.java @@ -61,7 +61,10 @@ public void onDestroy() { super.onDestroy(); } - @Nullable + /** + * @see ViewModelHelper#getViewModel() + */ + @NonNull @SuppressWarnings("unused") public R getViewModel() { return mViewModeHelper.getViewModel(); From feac6ce673aaaa886619d0297d40992bbe1f4e03 Mon Sep 17 00:00:00 2001 From: Daniel Novak Date: Mon, 2 May 2016 13:37:44 +0200 Subject: [PATCH 02/46] More consistent naming convention (#13) --- .../eu/inloop/viewmodel/AbstractViewModel.java | 14 ++++++++++---- .../java/eu/inloop/viewmodel/ViewModelHelper.java | 6 +++--- .../sample/viewmodel/UserListViewModel.java | 12 ++++++------ 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/library/src/main/java/eu/inloop/viewmodel/AbstractViewModel.java b/library/src/main/java/eu/inloop/viewmodel/AbstractViewModel.java index 915f37b..78fee2f 100644 --- a/library/src/main/java/eu/inloop/viewmodel/AbstractViewModel.java +++ b/library/src/main/java/eu/inloop/viewmodel/AbstractViewModel.java @@ -7,6 +7,7 @@ import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.util.Log; +import android.view.View; public abstract class AbstractViewModel { @@ -39,14 +40,19 @@ public String getUniqueIdentifier() { * {@link Activity#getIntent()}.{@link Intent#getExtras()} * @param savedInstanceState bundle with saved state, will be not null * only in case the system is killed due to low memory - * and restored (and {@link #saveState(Bundle)} returned a non-null bundle. + * and restored (and {@link #onSaveInstanceState(Bundle)} returned a non-null bundle. */ @SuppressWarnings("EmptyMethod") public void onCreate(@Nullable Bundle arguments, @Nullable Bundle savedInstanceState) { } - public void bindView(@NonNull T view) { + /** + * This method is an equivalent of {@link Fragment#onViewCreated(View, Bundle)} or {@link Activity#onCreate(Bundle)}. + * At this point, the View is ready and you can initialise it. + * @param view + */ + public void onBindView(@NonNull T view) { mBindViewWasCalled = true; mView = view; } @@ -61,7 +67,7 @@ public void clearView() { } @SuppressWarnings("EmptyMethod") - public void saveState(@NonNull final Bundle bundle) { + public void onSaveInstanceState(@NonNull final Bundle bundle) { } @@ -82,7 +88,7 @@ public void onStart() { * This is a good place to empty any planned tasks that are useless without a UI. */ @SuppressWarnings("EmptyMethod") - public void onModelRemoved() { + public void onDestroy() { } } diff --git a/library/src/main/java/eu/inloop/viewmodel/ViewModelHelper.java b/library/src/main/java/eu/inloop/viewmodel/ViewModelHelper.java index 2cbad96..e1061e3 100644 --- a/library/src/main/java/eu/inloop/viewmodel/ViewModelHelper.java +++ b/library/src/main/java/eu/inloop/viewmodel/ViewModelHelper.java @@ -71,7 +71,7 @@ public void setView(@NonNull final T view) { //no viewmodel for this fragment return; } - mViewModel.bindView(view); + mViewModel.onBindView(view); } /** @@ -176,7 +176,7 @@ public R getViewModel() { public void onSaveInstanceState(@NonNull Bundle bundle) { bundle.putString("identifier", mScreenId); if (mViewModel != null) { - mViewModel.saveState(bundle); + mViewModel.onSaveInstanceState(bundle); mOnSaveInstanceCalled = true; } } @@ -184,7 +184,7 @@ public void onSaveInstanceState(@NonNull Bundle bundle) { private void removeViewModel(@NonNull final Activity activity) { if (mViewModel != null && !mModelRemoved) { getViewModelProvider(activity).getViewModelProvider().remove(mScreenId); - mViewModel.onModelRemoved(); + mViewModel.onDestroy(); mModelRemoved = true; } } diff --git a/sample/src/main/java/eu/inloop/viewmodel/sample/viewmodel/UserListViewModel.java b/sample/src/main/java/eu/inloop/viewmodel/sample/viewmodel/UserListViewModel.java index c9ecda5..04f4581 100644 --- a/sample/src/main/java/eu/inloop/viewmodel/sample/viewmodel/UserListViewModel.java +++ b/sample/src/main/java/eu/inloop/viewmodel/sample/viewmodel/UserListViewModel.java @@ -32,8 +32,8 @@ public void onCreate(@Nullable Bundle arguments, @Nullable Bundle savedInstanceS } @Override - public void bindView(@NonNull IUserListView view) { - super.bindView(view); + public void onBindView(@NonNull IUserListView view) { + super.onBindView(view); //downloading list of users if (mLoadedUsers != null) { @@ -127,16 +127,16 @@ protected void onPostExecute(Void aVoid) { @Override - public void saveState(@NonNull final Bundle bundle) { - super.saveState(bundle); + public void onSaveInstanceState(@NonNull final Bundle bundle) { + super.onSaveInstanceState(bundle); if (mLoadedUsers != null) { bundle.putStringArrayList("userlist", new ArrayList<>(mLoadedUsers)); } } @Override - public void onModelRemoved() { - super.onModelRemoved(); + public void onDestroy() { + super.onDestroy(); //use this to cancel any planned requests } } From 2167582725b350ceefec339988ee2408d0fb65ad Mon Sep 17 00:00:00 2001 From: Daniel Novak Date: Mon, 2 May 2016 13:47:08 +0200 Subject: [PATCH 03/46] Bump version to 1.0.0 --- CHANGELOG.md | 6 ++++++ README.md | 4 +--- gradle.properties | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 053c351..f5fa641 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.0.0(2016-05-02) + + - We decided it's time for 1.0.0 release after a lot of use in production projects. + - Made getViewModel() as NonNull to prevent a lot of unncessary null checks. It will now throw an IllegalStateException in case it's null (should not happen under normal conditions. Only if you call it too soon - before Activity.onCreate or Fragment.onCreate). + - Breaking change: AbstractViewModel method saveState was renamed to onSaveInstanceState, bindView to onBindView and onModelRemoved to onDestroy. You may need to update your Models if you are overriding those methods. + ## 0.4.1(2016-01-22) - Added ViewModelBaseEmptyActivity - which you can extend in case you don't need a ViewModel in your activity (but your fragments have ViewModels). diff --git a/README.md b/README.md index d26f177..601a92d 100644 --- a/README.md +++ b/README.md @@ -106,10 +106,8 @@ Download Grab via Gradle: ```groovy -compile 'eu.inloop:androidviewmodel:0.4.0' +compile 'eu.inloop:androidviewmodel:1.0.0' ``` Build and study sample application from source code or download from Google Play.
[![](website/static/google_play.png)](https://play.google.com/store/apps/details?id=eu.inloop.viewmodel.sample) - -Development status: Used internally at company on some production applications. Library is under development and API changes might occur anytime. But it should be usuable at this point without any big issues (like memory leaks). diff --git a/gradle.properties b/gradle.properties index 19c76bc..a8aab98 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,4 +17,4 @@ # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true -VERSION_NAME=0.4.1 \ No newline at end of file +VERSION_NAME=1.0.0 \ No newline at end of file From 7c95a1bec29a4357cf95aef62f1f97e2a9979600 Mon Sep 17 00:00:00 2001 From: Daniel Novak Date: Wed, 14 Sep 2016 10:07:07 +0200 Subject: [PATCH 04/46] Update dependencies --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 3 ++- library/build.gradle | 8 ++++---- sample/build.gradle | 12 ++++++------ 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/build.gradle b/build.gradle index c06351a..128f763 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.2.3' + classpath 'com.android.tools.build:gradle:2.1.3' } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0bffd23..8fdb61d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Wed Sep 14 09:54:42 CEST 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/library/build.gradle b/library/build.gradle index e438b14..9a6e438 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -2,12 +2,12 @@ apply plugin: 'com.android.library' apply plugin: 'maven' android { - compileSdkVersion 23 - buildToolsVersion '23.0.2' + compileSdkVersion 24 + buildToolsVersion '24.0.2' defaultConfig { minSdkVersion 15 - targetSdkVersion 23 + targetSdkVersion 24 versionCode 1 versionName VERSION_NAME } @@ -24,7 +24,7 @@ android { } dependencies { - compile 'com.android.support:appcompat-v7:23.1.1' + compile 'com.android.support:appcompat-v7:24.2.0' } task androidJavadocs(type: Javadoc) { diff --git a/sample/build.gradle b/sample/build.gradle index bd35607..38b5c90 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -1,13 +1,13 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 23 - buildToolsVersion '23.0.2' + compileSdkVersion 24 + buildToolsVersion '24.0.2' defaultConfig { applicationId 'eu.inloop.viewmodel.sample' minSdkVersion 15 - targetSdkVersion 23 + targetSdkVersion 24 versionCode 1 versionName '1.0' } @@ -26,9 +26,9 @@ android { } dependencies { - compile 'com.android.support:appcompat-v7:23.1.1' - debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3' - releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3' + compile 'com.android.support:appcompat-v7:24.2.0' + debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4' + releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4' compile 'com.jakewharton:butterknife:5.1.2' compile project(':library') } From 956b8a0f6e890262bbbfa136143a6ffabf1c4853 Mon Sep 17 00:00:00 2001 From: Daniel Novak Date: Wed, 14 Sep 2016 10:10:06 +0200 Subject: [PATCH 05/46] Bump version to 1.0.1 --- CHANGELOG.md | 4 ++++ README.md | 2 +- gradle.properties | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5fa641..2b531b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.1(2016-9-14) + + - Updated dependencies (gradle, build tools, support library). + ## 1.0.0(2016-05-02) - We decided it's time for 1.0.0 release after a lot of use in production projects. diff --git a/README.md b/README.md index 601a92d..8d61abf 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ Download Grab via Gradle: ```groovy -compile 'eu.inloop:androidviewmodel:1.0.0' +compile 'eu.inloop:androidviewmodel:1.0.1' ``` Build and study sample application from source code or download from Google Play.
diff --git a/gradle.properties b/gradle.properties index a8aab98..9bedffc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,4 +17,4 @@ # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true -VERSION_NAME=1.0.0 \ No newline at end of file +VERSION_NAME=1.0.1 \ No newline at end of file From 74ba8ee6437f1cb27fad3e94f1dd946f08903efc Mon Sep 17 00:00:00 2001 From: Daniel Novak Date: Thu, 29 Sep 2016 09:00:26 +0200 Subject: [PATCH 06/46] Add annotations and cleanup code --- build.gradle | 2 +- library/build.gradle | 2 +- .../inloop/viewmodel/AbstractViewModel.java | 13 ++++---- .../inloop/viewmodel/IViewModelProvider.java | 3 ++ .../eu/inloop/viewmodel/ViewModelHelper.java | 30 +++++++++++++++---- .../inloop/viewmodel/ViewModelProvider.java | 30 +++++++++++-------- .../viewmodel/base/ViewModelBaseActivity.java | 8 +++++ .../base/ViewModelBaseEmptyActivity.java | 8 +++++ .../viewmodel/base/ViewModelBaseFragment.java | 8 +++++ sample/build.gradle | 2 +- .../viewmodel/sample/SampleApplication.java | 3 +- 11 files changed, 82 insertions(+), 27 deletions(-) diff --git a/build.gradle b/build.gradle index 128f763..03c181e 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.3' + classpath 'com.android.tools.build:gradle:2.2.0' } } diff --git a/library/build.gradle b/library/build.gradle index 9a6e438..2d7c20a 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -24,7 +24,7 @@ android { } dependencies { - compile 'com.android.support:appcompat-v7:24.2.0' + compile 'com.android.support:appcompat-v7:24.2.1' } task androidJavadocs(type: Javadoc) { diff --git a/library/src/main/java/eu/inloop/viewmodel/AbstractViewModel.java b/library/src/main/java/eu/inloop/viewmodel/AbstractViewModel.java index 78fee2f..96c1e6c 100644 --- a/library/src/main/java/eu/inloop/viewmodel/AbstractViewModel.java +++ b/library/src/main/java/eu/inloop/viewmodel/AbstractViewModel.java @@ -3,6 +3,7 @@ import android.app.Activity; import android.content.Intent; import android.os.Bundle; +import android.support.annotation.CallSuper; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; @@ -42,7 +43,7 @@ public String getUniqueIdentifier() { * only in case the system is killed due to low memory * and restored (and {@link #onSaveInstanceState(Bundle)} returned a non-null bundle. */ - @SuppressWarnings("EmptyMethod") + @SuppressWarnings({"EmptyMethod", "UnusedParameters"}) public void onCreate(@Nullable Bundle arguments, @Nullable Bundle savedInstanceState) { } @@ -50,8 +51,9 @@ public void onCreate(@Nullable Bundle arguments, @Nullable Bundle savedInstanceS /** * This method is an equivalent of {@link Fragment#onViewCreated(View, Bundle)} or {@link Activity#onCreate(Bundle)}. * At this point, the View is ready and you can initialise it. - * @param view + * @param view - View assigned to this ViewModel */ + @CallSuper public void onBindView(@NonNull T view) { mBindViewWasCalled = true; mView = view; @@ -62,21 +64,22 @@ public T getView() { return mView; } + @CallSuper public void clearView() { mView = null; } - @SuppressWarnings("EmptyMethod") + @SuppressWarnings({"EmptyMethod", "UnusedParameters"}) public void onSaveInstanceState(@NonNull final Bundle bundle) { } - @SuppressWarnings("EmptyMethod") + @SuppressWarnings({"EmptyMethod", "WeakerAccess"}) public void onStop() { } - @SuppressWarnings("EmptyMethod") + @SuppressWarnings({"EmptyMethod", "WeakerAccess"}) public void onStart() { if (mView == null && !mBindViewWasCalled) { Log.e("AndroidViewModel", this.getClass().getSimpleName() + " - no view associated. You probably did not call setModelView() in your Fragment or Activity"); diff --git a/library/src/main/java/eu/inloop/viewmodel/IViewModelProvider.java b/library/src/main/java/eu/inloop/viewmodel/IViewModelProvider.java index 62edc95..70e4555 100644 --- a/library/src/main/java/eu/inloop/viewmodel/IViewModelProvider.java +++ b/library/src/main/java/eu/inloop/viewmodel/IViewModelProvider.java @@ -1,5 +1,7 @@ package eu.inloop.viewmodel; +import android.support.annotation.Nullable; + /** * Your {@link android.app.Activity} must implement this interface if * any of the contained Fragments the {@link eu.inloop.viewmodel.ViewModelHelper} @@ -10,5 +12,6 @@ public interface IViewModelProvider { * See {@link eu.inloop.viewmodel.base.ViewModelBaseActivity} on how to implement. * @return the {@link ViewModelProvider}. */ + @Nullable ViewModelProvider getViewModelProvider(); } \ No newline at end of file diff --git a/library/src/main/java/eu/inloop/viewmodel/ViewModelHelper.java b/library/src/main/java/eu/inloop/viewmodel/ViewModelHelper.java index e1061e3..e8b9bfc 100644 --- a/library/src/main/java/eu/inloop/viewmodel/ViewModelHelper.java +++ b/library/src/main/java/eu/inloop/viewmodel/ViewModelHelper.java @@ -12,10 +12,15 @@ public class ViewModelHelper> { + @NonNull + private static final String STATE_STRING_SCREEN_IDENTIFIER = ViewModelHelper.class + ".state.string.identifier"; //NON-NLS + @Nullable private String mScreenId; + @Nullable private R mViewModel; + private boolean mModelRemoved; private boolean mOnSaveInstanceCalled; @@ -43,19 +48,29 @@ public void onCreate(@NonNull Activity activity, if (savedInstanceState == null) { mScreenId = UUID.randomUUID().toString(); } else { - mScreenId = savedInstanceState.getString("identifier"); + mScreenId = savedInstanceState.getString(STATE_STRING_SCREEN_IDENTIFIER); + if (null == mScreenId) { + throw new IllegalStateException("Bundle from onSaveInstanceState() didn't contain screen identifier. " + //NON-NLS + "Did you call ViewModelHelper.onSaveInstanceState?"); //NON-NLS + } + mOnSaveInstanceCalled = false; } // get model instance for this screen - final ViewModelProvider.ViewModelWrapper viewModelWrapper = getViewModelProvider(activity).getViewModelProvider().getViewModel(mScreenId, viewModelClass); + final ViewModelProvider viewModelProvider = getViewModelProvider(activity).getViewModelProvider(); + if (null == viewModelProvider) { + throw new IllegalStateException("ViewModelProvider for activity " + activity + " was null."); //NON-NLS + } + + final ViewModelProvider.ViewModelWrapper viewModelWrapper = viewModelProvider.getViewModel(mScreenId, viewModelClass); //noinspection unchecked mViewModel = (R) viewModelWrapper.viewModel; if (viewModelWrapper.wasCreated) { // detect that the system has killed the app - saved instance is not null, but the model was recreated if (BuildConfig.DEBUG && savedInstanceState != null) { - Log.d("model", "Fragment recreated by system - restoring viewmodel"); + Log.d("model", "Fragment recreated by system - restoring viewmodel"); //NON-NLS } mViewModel.onCreate(arguments, savedInstanceState); } @@ -174,7 +189,7 @@ public R getViewModel() { * @param bundle bundle */ public void onSaveInstanceState(@NonNull Bundle bundle) { - bundle.putString("identifier", mScreenId); + bundle.putString(STATE_STRING_SCREEN_IDENTIFIER, mScreenId); if (mViewModel != null) { mViewModel.onSaveInstanceState(bundle); mOnSaveInstanceCalled = true; @@ -183,12 +198,17 @@ public void onSaveInstanceState(@NonNull Bundle bundle) { private void removeViewModel(@NonNull final Activity activity) { if (mViewModel != null && !mModelRemoved) { - getViewModelProvider(activity).getViewModelProvider().remove(mScreenId); + final ViewModelProvider viewModelProvider = getViewModelProvider(activity).getViewModelProvider(); + if (null == viewModelProvider) { + throw new IllegalStateException("ViewModelProvider for activity " + activity + " was null."); //NON-NLS + } + viewModelProvider.remove(mScreenId); mViewModel.onDestroy(); mModelRemoved = true; } } + @NonNull private IViewModelProvider getViewModelProvider(@NonNull Activity activity) { if (!(activity instanceof IViewModelProvider)) { throw new IllegalStateException("Your activity must implement IViewModelProvider"); //NON-NLS diff --git a/library/src/main/java/eu/inloop/viewmodel/ViewModelProvider.java b/library/src/main/java/eu/inloop/viewmodel/ViewModelProvider.java index 4559190..c541de4 100644 --- a/library/src/main/java/eu/inloop/viewmodel/ViewModelProvider.java +++ b/library/src/main/java/eu/inloop/viewmodel/ViewModelProvider.java @@ -1,6 +1,7 @@ package eu.inloop.viewmodel; import android.app.Activity; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v4.app.FragmentActivity; import java.util.HashMap; @@ -13,23 +14,26 @@ */ public class ViewModelProvider { + @NonNull private final HashMap> mViewModelCache; + @NonNull public static ViewModelProvider newInstance(@NonNull final FragmentActivity activity) { if (activity.getLastCustomNonConfigurationInstance() == null) { return new ViewModelProvider(); } else { - return (ViewModelProvider) activity.getLastCustomNonConfigurationInstance(); + return (ViewModelProvider) activity.getLastCustomNonConfigurationInstance(); } } @SuppressWarnings({"deprecation", "unused"}) + @NonNull @Deprecated public static ViewModelProvider newInstance(@NonNull final Activity activity) { if (activity.getLastNonConfigurationInstance() == null) { return new ViewModelProvider(); } else { - return (ViewModelProvider) activity.getLastNonConfigurationInstance(); + return (ViewModelProvider) activity.getLastNonConfigurationInstance(); } } @@ -37,7 +41,7 @@ private ViewModelProvider() { mViewModelCache = new HashMap<>(); } - public synchronized void remove(String modeIdentifier) { + public synchronized void remove(@Nullable String modeIdentifier) { mViewModelCache.remove(modeIdentifier); } @@ -47,8 +51,8 @@ public synchronized void removeAllViewModels() { @SuppressWarnings("unchecked") @NonNull - public synchronized ViewModelWrapper getViewModel(final String modelIdentifier, - final @NonNull Class> viewModelClass) { + public synchronized ViewModelWrapper getViewModel(@NonNull final String modelIdentifier, + @NonNull final Class> viewModelClass) { AbstractViewModel instance = (AbstractViewModel) mViewModelCache.get(modelIdentifier); if (instance != null) { return new ViewModelWrapper<>(instance, false); @@ -56,20 +60,20 @@ public synchronized ViewModelWrapper getViewModel(final Str try { instance = viewModelClass.newInstance(); - instance.setUniqueIdentifier(modelIdentifier); - mViewModelCache.put(modelIdentifier, instance); - return new ViewModelWrapper<>(instance, true); - } catch (Exception ex) { + } catch (final Exception ex) { throw new RuntimeException(ex); } + instance.setUniqueIdentifier(modelIdentifier); + mViewModelCache.put(modelIdentifier, instance); + return new ViewModelWrapper<>(instance, true); } - public static class ViewModelWrapper { + final static class ViewModelWrapper { @NonNull - public final AbstractViewModel viewModel; - public final boolean wasCreated; + final AbstractViewModel viewModel; + final boolean wasCreated; - private ViewModelWrapper(@NonNull AbstractViewModel mViewModel, boolean mWasCreated) { + private ViewModelWrapper(@NonNull AbstractViewModel mViewModel, final boolean mWasCreated) { this.viewModel = mViewModel; this.wasCreated = mWasCreated; } diff --git a/library/src/main/java/eu/inloop/viewmodel/base/ViewModelBaseActivity.java b/library/src/main/java/eu/inloop/viewmodel/base/ViewModelBaseActivity.java index cc86e98..bb59d88 100644 --- a/library/src/main/java/eu/inloop/viewmodel/base/ViewModelBaseActivity.java +++ b/library/src/main/java/eu/inloop/viewmodel/base/ViewModelBaseActivity.java @@ -1,6 +1,7 @@ package eu.inloop.viewmodel.base; import android.os.Bundle; +import android.support.annotation.CallSuper; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -10,8 +11,10 @@ public abstract class ViewModelBaseActivity> extends ViewModelBaseEmptyActivity implements IView { + @NonNull private final ViewModelHelper mViewModeHelper = new ViewModelHelper<>(); + @CallSuper @Override protected void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -27,26 +30,31 @@ public void setModelView(@NonNull final T view) { mViewModeHelper.setView(view); } + @Nullable public abstract Class getViewModelClass(); + @CallSuper @Override public void onSaveInstanceState(@NonNull final Bundle outState) { super.onSaveInstanceState(outState); mViewModeHelper.onSaveInstanceState(outState); } + @CallSuper @Override public void onStart() { super.onStart(); mViewModeHelper.onStart(); } + @CallSuper @Override public void onStop() { super.onStop(); mViewModeHelper.onStop(); } + @CallSuper @Override public void onDestroy() { mViewModeHelper.onDestroy(this); diff --git a/library/src/main/java/eu/inloop/viewmodel/base/ViewModelBaseEmptyActivity.java b/library/src/main/java/eu/inloop/viewmodel/base/ViewModelBaseEmptyActivity.java index 05395d7..35ebd20 100644 --- a/library/src/main/java/eu/inloop/viewmodel/base/ViewModelBaseEmptyActivity.java +++ b/library/src/main/java/eu/inloop/viewmodel/base/ViewModelBaseEmptyActivity.java @@ -1,6 +1,7 @@ package eu.inloop.viewmodel.base; import android.os.Bundle; +import android.support.annotation.CallSuper; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; @@ -14,8 +15,10 @@ */ public abstract class ViewModelBaseEmptyActivity extends AppCompatActivity implements IViewModelProvider { + @Nullable private ViewModelProvider mViewModelProvider; + @CallSuper @Override protected void onCreate(@Nullable final Bundle savedInstanceState) { //This code must be execute prior to super.onCreate() @@ -29,14 +32,19 @@ public Object onRetainCustomNonConfigurationInstance() { return mViewModelProvider; } + @CallSuper @Override public void onStop() { super.onStop(); if (isFinishing()) { + if (null == mViewModelProvider) { + throw new IllegalStateException("ViewModelProvider for activity " + this + " was null."); //NON-NLS + } mViewModelProvider.removeAllViewModels(); } } + @Nullable @Override public ViewModelProvider getViewModelProvider() { return mViewModelProvider; diff --git a/library/src/main/java/eu/inloop/viewmodel/base/ViewModelBaseFragment.java b/library/src/main/java/eu/inloop/viewmodel/base/ViewModelBaseFragment.java index a6ba060..485d070 100644 --- a/library/src/main/java/eu/inloop/viewmodel/base/ViewModelBaseFragment.java +++ b/library/src/main/java/eu/inloop/viewmodel/base/ViewModelBaseFragment.java @@ -1,6 +1,7 @@ package eu.inloop.viewmodel.base; import android.os.Bundle; +import android.support.annotation.CallSuper; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; @@ -12,8 +13,10 @@ public abstract class ViewModelBaseFragment> extends Fragment implements IView { + @NonNull private final ViewModelHelper mViewModeHelper = new ViewModelHelper<>(); + @CallSuper @Override public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -31,30 +34,35 @@ protected void setModelView(@NonNull final T view) { mViewModeHelper.setView(view); } + @CallSuper @Override public void onSaveInstanceState(@NonNull final Bundle outState) { super.onSaveInstanceState(outState); mViewModeHelper.onSaveInstanceState(outState); } + @CallSuper @Override public void onStart() { super.onStart(); mViewModeHelper.onStart(); } + @CallSuper @Override public void onStop() { super.onStop(); mViewModeHelper.onStop(); } + @CallSuper @Override public void onDestroyView() { mViewModeHelper.onDestroyView(this); super.onDestroyView(); } + @CallSuper @Override public void onDestroy() { mViewModeHelper.onDestroy(this); diff --git a/sample/build.gradle b/sample/build.gradle index 38b5c90..93c8615 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -26,7 +26,7 @@ android { } dependencies { - compile 'com.android.support:appcompat-v7:24.2.0' + compile 'com.android.support:appcompat-v7:24.2.1' debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4' releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4' compile 'com.jakewharton:butterknife:5.1.2' diff --git a/sample/src/main/java/eu/inloop/viewmodel/sample/SampleApplication.java b/sample/src/main/java/eu/inloop/viewmodel/sample/SampleApplication.java index 22452b3..0fe00cb 100644 --- a/sample/src/main/java/eu/inloop/viewmodel/sample/SampleApplication.java +++ b/sample/src/main/java/eu/inloop/viewmodel/sample/SampleApplication.java @@ -10,7 +10,8 @@ public class SampleApplication extends Application { private RefWatcher refWatcher; - @Override public void onCreate() { + @Override + public void onCreate() { super.onCreate(); refWatcher = LeakCanary.install(this); } From 6bc949085264ff758af78afee0ee0f16f359a5d7 Mon Sep 17 00:00:00 2001 From: Stepan Sanda Date: Mon, 21 Nov 2016 10:45:37 +0100 Subject: [PATCH 07/46] Add databinding support Base support for binding Add sample with simple binding Code cleaning Cleaning code - removing unnecessary helpers Add cast exception Add annotatoins, code cleaning and refactoring Remove implementation of getViewModelBindingConfig from ViewModelBaseFragment --- build.gradle | 2 +- library/build.gradle | 4 ++ .../main/java/eu/inloop/viewmodel/IView.java | 13 +++- .../eu/inloop/viewmodel/ViewModelHelper.java | 67 +++++++++++++++++-- .../viewmodel/base/ViewModelBaseFragment.java | 43 +++++++----- .../binding/ViewModelBaseBindingFragment.java | 48 +++++++++++++ .../binding/ViewModelBindingConfig.java | 59 ++++++++++++++++ .../layout/binding_variable_placeholder.xml | 12 ++++ sample/build.gradle | 4 ++ sample/src/main/AndroidManifest.xml | 6 +- .../activity/SampleBindingActivity.java | 29 ++++++++ .../sample/fragment/PagerFragment.java | 8 ++- .../fragment/SampleBindingFragment.java | 35 ++++++++++ .../sample/fragment/SampleBundleFragment.java | 8 ++- .../sample/fragment/UserListFragment.java | 20 +++++- .../viewmodel/SampleBindingViewModel.java | 33 +++++++++ .../sample/viewmodel/view/IPageView.java | 5 ++ .../viewmodel/view/ISampleBindingView.java | 10 +++ .../res/layout/activity_sample_binding.xml | 7 ++ .../res/layout/fragment_sample_binding.xml | 29 ++++++++ .../src/main/res/layout/fragment_userlist.xml | 8 +++ 21 files changed, 418 insertions(+), 32 deletions(-) create mode 100644 library/src/main/java/eu/inloop/viewmodel/binding/ViewModelBaseBindingFragment.java create mode 100644 library/src/main/java/eu/inloop/viewmodel/binding/ViewModelBindingConfig.java create mode 100644 library/src/main/res/layout/binding_variable_placeholder.xml create mode 100644 sample/src/main/java/eu/inloop/viewmodel/sample/activity/SampleBindingActivity.java create mode 100644 sample/src/main/java/eu/inloop/viewmodel/sample/fragment/SampleBindingFragment.java create mode 100644 sample/src/main/java/eu/inloop/viewmodel/sample/viewmodel/SampleBindingViewModel.java create mode 100644 sample/src/main/java/eu/inloop/viewmodel/sample/viewmodel/view/ISampleBindingView.java create mode 100644 sample/src/main/res/layout/activity_sample_binding.xml create mode 100644 sample/src/main/res/layout/fragment_sample_binding.xml diff --git a/build.gradle b/build.gradle index 03c181e..bbdda21 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.0' + classpath 'com.android.tools.build:gradle:2.2.2' } } diff --git a/library/build.gradle b/library/build.gradle index 2d7c20a..30344aa 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -21,6 +21,10 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + + dataBinding { + enabled = true; + } } dependencies { diff --git a/library/src/main/java/eu/inloop/viewmodel/IView.java b/library/src/main/java/eu/inloop/viewmodel/IView.java index 300c4b3..4a06026 100644 --- a/library/src/main/java/eu/inloop/viewmodel/IView.java +++ b/library/src/main/java/eu/inloop/viewmodel/IView.java @@ -1,5 +1,16 @@ package eu.inloop.viewmodel; -public interface IView { +import android.support.annotation.Nullable; + +import eu.inloop.viewmodel.binding.ViewModelBindingConfig; +public interface IView { + /** + * This method is used for Data Binding to bind correct layout and variable atomatically + * Can return null value in case that Data Binding is not used. + * + * @return defined ViewModelBinding Config for a specific screen. + */ + @Nullable + ViewModelBindingConfig getViewModelBindingConfig(); } diff --git a/library/src/main/java/eu/inloop/viewmodel/ViewModelHelper.java b/library/src/main/java/eu/inloop/viewmodel/ViewModelHelper.java index e8b9bfc..322cc88 100644 --- a/library/src/main/java/eu/inloop/viewmodel/ViewModelHelper.java +++ b/library/src/main/java/eu/inloop/viewmodel/ViewModelHelper.java @@ -2,14 +2,19 @@ import android.app.Activity; import android.content.Intent; +import android.databinding.DataBindingUtil; +import android.databinding.ViewDataBinding; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.util.Log; +import android.view.LayoutInflater; import java.util.UUID; +import eu.inloop.viewmodel.binding.ViewModelBindingConfig; + public class ViewModelHelper> { @NonNull @@ -21,18 +26,22 @@ public class ViewModelHelper> { @Nullable private R mViewModel; + @Nullable + private ViewDataBinding mBinding; + private boolean mModelRemoved; private boolean mOnSaveInstanceCalled; /** * Call from {@link android.app.Activity#onCreate(android.os.Bundle)} or * {@link android.support.v4.app.Fragment#onCreate(android.os.Bundle)} - * @param activity parent activity + * + * @param activity parent activity * @param savedInstanceState savedInstance state from {@link Activity#onCreate(Bundle)} or * {@link Fragment#onCreate(Bundle)} - * @param viewModelClass the {@link Class} of your ViewModel - * @param arguments pass {@link Fragment#getArguments()} or - * {@link Activity#getIntent()}.{@link Intent#getExtras() getExtras()} + * @param viewModelClass the {@link Class} of your ViewModel + * @param arguments pass {@link Fragment#getArguments()} or + * {@link Activity#getIntent()}.{@link Intent#getExtras() getExtras()} */ public void onCreate(@NonNull Activity activity, @Nullable Bundle savedInstanceState, @@ -62,7 +71,7 @@ public void onCreate(@NonNull Activity activity, if (null == viewModelProvider) { throw new IllegalStateException("ViewModelProvider for activity " + activity + " was null."); //NON-NLS } - + final ViewModelProvider.ViewModelWrapper viewModelWrapper = viewModelProvider.getViewModel(mScreenId, viewModelClass); //noinspection unchecked mViewModel = (R) viewModelWrapper.viewModel; @@ -79,6 +88,7 @@ public void onCreate(@NonNull Activity activity, /** * Call from {@link android.support.v4.app.Fragment#onViewCreated(android.view.View, android.os.Bundle)} * or {@link android.app.Activity#onCreate(android.os.Bundle)} + * * @param view view */ public void setView(@NonNull final T view) { @@ -89,10 +99,44 @@ public void setView(@NonNull final T view) { mViewModel.onBindView(view); } + public void performBinding(@NonNull final IView bindingView) { + // skip if already create + if (mBinding != null) { + return; + } + + // get ViewModelBinding config + final ViewModelBindingConfig viewModelConfig = bindingView.getViewModelBindingConfig(); + // if fragment not providing ViewModelBindingConfig, do not perform binding operations + if (viewModelConfig == null) { + return; + } + + // perform Data Binding initialization + final ViewDataBinding viewDataBinding; + if (bindingView instanceof Activity) { + viewDataBinding = DataBindingUtil.setContentView(((Activity) bindingView), viewModelConfig.getLayoutResource()); + } else if (bindingView instanceof Fragment) { + viewDataBinding = DataBindingUtil.inflate(LayoutInflater.from(viewModelConfig.getContext()), viewModelConfig.getLayoutResource(), null, false); + } else { + throw new IllegalArgumentException("View must be an instance of Activity or Fragment (support-v4)."); + } + + // bind all together + if (!viewDataBinding.setVariable(viewModelConfig.getViewModelVariableName(), getViewModel())) { + throw new IllegalArgumentException("Binding variable wasn't set successfully. Probably viewModelVariableName of your " + + "ViewModelBindingConfig of " + bindingView.getClass().getSimpleName() + " doesn't match any variable in " + + viewDataBinding.getClass().getSimpleName()); + } + + mBinding = viewDataBinding; + } + /** * Use in case this model is associated with an {@link android.support.v4.app.Fragment} * Call from {@link android.support.v4.app.Fragment#onDestroyView()}. Use in case model is associated * with Fragment + * * @param fragment fragment */ public void onDestroyView(@NonNull Fragment fragment) { @@ -104,11 +148,13 @@ public void onDestroyView(@NonNull Fragment fragment) { if (fragment.getActivity() != null && fragment.getActivity().isFinishing()) { removeViewModel(fragment.getActivity()); } + mBinding = null; } /** * Use in case this model is associated with an {@link android.support.v4.app.Fragment} * Call from {@link android.support.v4.app.Fragment#onDestroy()} + * * @param fragment fragment */ public void onDestroy(@NonNull final Fragment fragment) { @@ -126,11 +172,13 @@ public void onDestroy(@NonNull final Fragment fragment) { } removeViewModel(fragment.getActivity()); } + mBinding = null; } /** * Use in case this model is associated with an {@link android.app.Activity} * Call from {@link android.app.Activity#onDestroy()} + * * @param activity activity */ public void onDestroy(@NonNull final Activity activity) { @@ -142,6 +190,7 @@ public void onDestroy(@NonNull final Activity activity) { if (activity.isFinishing()) { removeViewModel(activity); } + mBinding = null; } /** @@ -172,6 +221,7 @@ public void onStart() { * Throws an {@link IllegalStateException} in case the ViewModel is null. This can happen * if you call this method too soon - before {@link Activity#onCreate(Bundle)} or {@link Fragment#onCreate(Bundle)} * or this {@link ViewModelHelper} is not properly setup. + * * @return {@link R} */ @NonNull @@ -186,6 +236,7 @@ public R getViewModel() { * Call from {@link android.app.Activity#onSaveInstanceState(android.os.Bundle)} * or {@link android.support.v4.app.Fragment#onSaveInstanceState(android.os.Bundle)}. * This allows the model to save its state. + * * @param bundle bundle */ public void onSaveInstanceState(@NonNull Bundle bundle) { @@ -196,6 +247,11 @@ public void onSaveInstanceState(@NonNull Bundle bundle) { } } + @Nullable + public ViewDataBinding getBinding() { + return mBinding; + } + private void removeViewModel(@NonNull final Activity activity) { if (mViewModel != null && !mModelRemoved) { final ViewModelProvider viewModelProvider = getViewModelProvider(activity).getViewModelProvider(); @@ -205,6 +261,7 @@ private void removeViewModel(@NonNull final Activity activity) { viewModelProvider.remove(mScreenId); mViewModel.onDestroy(); mModelRemoved = true; + mBinding = null; } } diff --git a/library/src/main/java/eu/inloop/viewmodel/base/ViewModelBaseFragment.java b/library/src/main/java/eu/inloop/viewmodel/base/ViewModelBaseFragment.java index 485d070..354b7ed 100644 --- a/library/src/main/java/eu/inloop/viewmodel/base/ViewModelBaseFragment.java +++ b/library/src/main/java/eu/inloop/viewmodel/base/ViewModelBaseFragment.java @@ -20,61 +20,68 @@ public abstract class ViewModelBaseFragment getViewModelClass(); - - /** - * Call this after your view is ready - usually on the end of {@link Fragment#onViewCreated(View, Bundle)} - * @param view view - */ - protected void setModelView(@NonNull final T view) { - mViewModeHelper.setView(view); + getViewModeHelper().onCreate(getActivity(), savedInstanceState, getViewModelClass(), getArguments()); } @CallSuper @Override public void onSaveInstanceState(@NonNull final Bundle outState) { super.onSaveInstanceState(outState); - mViewModeHelper.onSaveInstanceState(outState); + getViewModeHelper().onSaveInstanceState(outState); } @CallSuper @Override public void onStart() { super.onStart(); - mViewModeHelper.onStart(); + getViewModeHelper().onStart(); } @CallSuper @Override public void onStop() { super.onStop(); - mViewModeHelper.onStop(); + getViewModeHelper().onStop(); } @CallSuper @Override public void onDestroyView() { - mViewModeHelper.onDestroyView(this); + getViewModeHelper().onDestroyView(this); super.onDestroyView(); } @CallSuper @Override public void onDestroy() { - mViewModeHelper.onDestroy(this); + getViewModeHelper().onDestroy(this); super.onDestroy(); } + @Nullable + public abstract Class getViewModelClass(); + /** * @see ViewModelHelper#getViewModel() */ @NonNull @SuppressWarnings("unused") public R getViewModel() { - return mViewModeHelper.getViewModel(); + return getViewModeHelper().getViewModel(); + } + + @NonNull + public ViewModelHelper getViewModeHelper() { + return mViewModeHelper; + } + + /** + * Call this after your view is ready - usually on the end of {@link + * Fragment#onViewCreated(View, Bundle)} + * + * @param view view + */ + protected void setModelView(@NonNull final T view) { + getViewModeHelper().setView(view); } } diff --git a/library/src/main/java/eu/inloop/viewmodel/binding/ViewModelBaseBindingFragment.java b/library/src/main/java/eu/inloop/viewmodel/binding/ViewModelBaseBindingFragment.java new file mode 100644 index 0000000..7b8fca8 --- /dev/null +++ b/library/src/main/java/eu/inloop/viewmodel/binding/ViewModelBaseBindingFragment.java @@ -0,0 +1,48 @@ +package eu.inloop.viewmodel.binding; + +import android.databinding.ViewDataBinding; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import org.jetbrains.annotations.NotNull; + +import eu.inloop.viewmodel.AbstractViewModel; +import eu.inloop.viewmodel.IView; +import eu.inloop.viewmodel.base.ViewModelBaseFragment; + +public abstract class ViewModelBaseBindingFragment, B extends ViewDataBinding> + extends ViewModelBaseFragment + implements IView { + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getViewModeHelper().performBinding(this); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + getViewModeHelper().performBinding(this); + final ViewDataBinding binding = getViewModeHelper().getBinding(); + if (binding != null) { + return binding.getRoot(); + } else { + throw new IllegalStateException("Binding cannot be null. Perform binding before calling getBinding()"); + } + } + + @SuppressWarnings("unused") + @NotNull + public B getBinding() { + try { + return (B) getViewModeHelper().getBinding(); + } catch (ClassCastException ex) { + throw new IllegalStateException("Method getViewModelBindingConfig() has to return same " + + "ViewDataBinding type as it is set to base Fragment"); + } + } +} diff --git a/library/src/main/java/eu/inloop/viewmodel/binding/ViewModelBindingConfig.java b/library/src/main/java/eu/inloop/viewmodel/binding/ViewModelBindingConfig.java new file mode 100644 index 0000000..d008c47 --- /dev/null +++ b/library/src/main/java/eu/inloop/viewmodel/binding/ViewModelBindingConfig.java @@ -0,0 +1,59 @@ +package eu.inloop.viewmodel.binding; + +import android.content.Context; +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; + +import eu.inloop.viewmodel.BR; + +/** + * Use this to define a ViewModelBinding Config for a specific screen. + *

+ * Config contains layout resource ID, Context, ViewModel binding variable name + */ +public class ViewModelBindingConfig { + + @LayoutRes + private final int mLayoutResource; + private final int mViewModelVariableName; + @NonNull + private final Context mContext; + + /** + * Create a ViewModelBinding Config object for an Activity/Fragment + * This constructor should be used if the binding variable is named differently + * + * @param layoutResource Layout resource ID + * @param viewModelVariableName Data Binding variable name for injecting the ViewModel - use + * generated id (e.g. BR.mViewModel) + */ + public ViewModelBindingConfig(@LayoutRes int layoutResource, int viewModelVariableName, @NonNull Context context) { + mLayoutResource = layoutResource; + mViewModelVariableName = viewModelVariableName; + mContext = context; + } + + /** + * Create a ViewModelBinding Config object for an Activity/Fragment + * Use this constructor if the binding variable is named viewModel + * + * @param layoutResource Layout resource ID + */ + public ViewModelBindingConfig(@LayoutRes int layoutResource, @NonNull Context context) { + this(layoutResource, BR.viewModel, context); + } + + @LayoutRes + public int getLayoutResource() { + return mLayoutResource; + } + + public int getViewModelVariableName() { + return mViewModelVariableName; + } + + @NonNull + public Context getContext() { + return mContext; + } +} diff --git a/library/src/main/res/layout/binding_variable_placeholder.xml b/library/src/main/res/layout/binding_variable_placeholder.xml new file mode 100644 index 0000000..32835f4 --- /dev/null +++ b/library/src/main/res/layout/binding_variable_placeholder.xml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/sample/build.gradle b/sample/build.gradle index 93c8615..b95bfe8 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -23,6 +23,10 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + + dataBinding { + enabled = true; + } } dependencies { diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 66c08c6..3e803de 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -5,19 +5,19 @@ + android:label="@string/app_name"> - + + diff --git a/sample/src/main/java/eu/inloop/viewmodel/sample/activity/SampleBindingActivity.java b/sample/src/main/java/eu/inloop/viewmodel/sample/activity/SampleBindingActivity.java new file mode 100644 index 0000000..9ea4528 --- /dev/null +++ b/sample/src/main/java/eu/inloop/viewmodel/sample/activity/SampleBindingActivity.java @@ -0,0 +1,29 @@ +package eu.inloop.viewmodel.sample.activity; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +import butterknife.ButterKnife; +import eu.inloop.viewmodel.base.ViewModelBaseEmptyActivity; +import eu.inloop.viewmodel.sample.R; +import eu.inloop.viewmodel.sample.fragment.SampleBindingFragment; + +public class SampleBindingActivity extends ViewModelBaseEmptyActivity { + + public static Intent newIntent(Context context) { + Intent intent = new Intent(context, SampleBindingActivity.class); + return intent; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + ButterKnife.inject(this); + + if (savedInstanceState == null) { + getSupportFragmentManager().beginTransaction().replace(R.id.root_content, new SampleBindingFragment(), "sample-binding-fragment").commit(); + } + } +} diff --git a/sample/src/main/java/eu/inloop/viewmodel/sample/fragment/PagerFragment.java b/sample/src/main/java/eu/inloop/viewmodel/sample/fragment/PagerFragment.java index 5168466..a866508 100644 --- a/sample/src/main/java/eu/inloop/viewmodel/sample/fragment/PagerFragment.java +++ b/sample/src/main/java/eu/inloop/viewmodel/sample/fragment/PagerFragment.java @@ -2,7 +2,6 @@ import android.os.Bundle; import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -11,6 +10,7 @@ import com.squareup.leakcanary.RefWatcher; import eu.inloop.viewmodel.base.ViewModelBaseFragment; +import eu.inloop.viewmodel.binding.ViewModelBindingConfig; import eu.inloop.viewmodel.sample.R; import eu.inloop.viewmodel.sample.SampleApplication; import eu.inloop.viewmodel.sample.viewmodel.PageModel; @@ -51,4 +51,10 @@ public void onDestroy() { RefWatcher refWatcher = SampleApplication.getRefWatcher(getActivity()); refWatcher.watch(this); } + + @Nullable + @Override + public ViewModelBindingConfig getViewModelBindingConfig() { + return null; + } } diff --git a/sample/src/main/java/eu/inloop/viewmodel/sample/fragment/SampleBindingFragment.java b/sample/src/main/java/eu/inloop/viewmodel/sample/fragment/SampleBindingFragment.java new file mode 100644 index 0000000..6db4912 --- /dev/null +++ b/sample/src/main/java/eu/inloop/viewmodel/sample/fragment/SampleBindingFragment.java @@ -0,0 +1,35 @@ +package eu.inloop.viewmodel.sample.fragment; + +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; + +import eu.inloop.viewmodel.binding.ViewModelBaseBindingFragment; +import eu.inloop.viewmodel.binding.ViewModelBindingConfig; +import eu.inloop.viewmodel.sample.R; +import eu.inloop.viewmodel.sample.databinding.FragmentSampleBindingBinding; +import eu.inloop.viewmodel.sample.viewmodel.SampleBindingViewModel; +import eu.inloop.viewmodel.sample.viewmodel.view.ISampleBindingView; + +/** + * A simple {@link Fragment} subclass. + */ +public class SampleBindingFragment + extends ViewModelBaseBindingFragment + implements ISampleBindingView { + + public SampleBindingFragment() { + // Required empty public constructor + } + + @Override + public ViewModelBindingConfig getViewModelBindingConfig() { + return new ViewModelBindingConfig(R.layout.fragment_sample_binding, getActivity()); + } + + @Nullable + @Override + public Class getViewModelClass() { + return SampleBindingViewModel.class; + } + +} diff --git a/sample/src/main/java/eu/inloop/viewmodel/sample/fragment/SampleBundleFragment.java b/sample/src/main/java/eu/inloop/viewmodel/sample/fragment/SampleBundleFragment.java index c3d6378..2b53568 100644 --- a/sample/src/main/java/eu/inloop/viewmodel/sample/fragment/SampleBundleFragment.java +++ b/sample/src/main/java/eu/inloop/viewmodel/sample/fragment/SampleBundleFragment.java @@ -2,7 +2,6 @@ import android.os.Bundle; import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -10,6 +9,7 @@ import butterknife.ButterKnife; import eu.inloop.viewmodel.IView; import eu.inloop.viewmodel.base.ViewModelBaseFragment; +import eu.inloop.viewmodel.binding.ViewModelBindingConfig; import eu.inloop.viewmodel.sample.R; import eu.inloop.viewmodel.sample.viewmodel.SampleArgumentViewModel; @@ -41,4 +41,10 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { public Class getViewModelClass() { return SampleArgumentViewModel.class; } + + @Nullable + @Override + public ViewModelBindingConfig getViewModelBindingConfig() { + return null; + } } diff --git a/sample/src/main/java/eu/inloop/viewmodel/sample/fragment/UserListFragment.java b/sample/src/main/java/eu/inloop/viewmodel/sample/fragment/UserListFragment.java index 7c3d759..1187c03 100644 --- a/sample/src/main/java/eu/inloop/viewmodel/sample/fragment/UserListFragment.java +++ b/sample/src/main/java/eu/inloop/viewmodel/sample/fragment/UserListFragment.java @@ -8,6 +8,7 @@ import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ArrayAdapter; +import android.widget.Button; import android.widget.ListView; import android.widget.TextView; @@ -19,8 +20,10 @@ import butterknife.ButterKnife; import butterknife.InjectView; import eu.inloop.viewmodel.base.ViewModelBaseFragment; +import eu.inloop.viewmodel.binding.ViewModelBindingConfig; import eu.inloop.viewmodel.sample.R; import eu.inloop.viewmodel.sample.SampleApplication; +import eu.inloop.viewmodel.sample.activity.SampleBindingActivity; import eu.inloop.viewmodel.sample.activity.ViewPagerActivity; import eu.inloop.viewmodel.sample.viewmodel.UserListViewModel; import eu.inloop.viewmodel.sample.viewmodel.view.IUserListView; @@ -33,6 +36,8 @@ public class UserListFragment extends ViewModelBaseFragment mAdapter; @@ -47,7 +52,6 @@ public Class getViewModelClass() { return UserListViewModel.class; } - @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.fragment_userlist, container, false); @@ -74,6 +78,12 @@ public void onClick(View view) { } }); mListview.addHeaderView(headerView, null, false); + mOpenBindingFragment.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + startActivity(SampleBindingActivity.newIntent(getActivity())); + } + }); return view; } @@ -98,7 +108,7 @@ public void showUsers(List users) { mAdapter.setNotifyOnChange(true); mAdapter.notifyDataSetChanged(); } - + @Override public void showLoading(float progress) { mProgressView.setVisibility(View.VISIBLE); @@ -118,4 +128,10 @@ public void onDestroy() { RefWatcher refWatcher = SampleApplication.getRefWatcher(getActivity()); refWatcher.watch(this); } + + @Nullable + @Override + public ViewModelBindingConfig getViewModelBindingConfig() { + return null; + } } diff --git a/sample/src/main/java/eu/inloop/viewmodel/sample/viewmodel/SampleBindingViewModel.java b/sample/src/main/java/eu/inloop/viewmodel/sample/viewmodel/SampleBindingViewModel.java new file mode 100644 index 0000000..40a74e5 --- /dev/null +++ b/sample/src/main/java/eu/inloop/viewmodel/sample/viewmodel/SampleBindingViewModel.java @@ -0,0 +1,33 @@ +package eu.inloop.viewmodel.sample.viewmodel; + +import android.databinding.ObservableField; +import android.os.Bundle; +import android.support.annotation.Nullable; + +import eu.inloop.viewmodel.AbstractViewModel; +import eu.inloop.viewmodel.sample.viewmodel.view.ISampleBindingView; + +/** + * Created by stepansanda on 21/11/2016. + */ + +public class SampleBindingViewModel extends AbstractViewModel { + + public final ObservableField text = new ObservableField<>(); + private int mButtonClickedCounter = 0; + + @Override + public void onCreate(@Nullable Bundle arguments, @Nullable Bundle savedInstanceState) { + super.onCreate(arguments, savedInstanceState); + setButtonClickedText(); + } + + public void onButtonClick() { + mButtonClickedCounter++; + setButtonClickedText(); + } + + private void setButtonClickedText() { + text.set("Button Clicked: " + mButtonClickedCounter + " times"); + } +} diff --git a/sample/src/main/java/eu/inloop/viewmodel/sample/viewmodel/view/IPageView.java b/sample/src/main/java/eu/inloop/viewmodel/sample/viewmodel/view/IPageView.java index fab1625..ab4112b 100644 --- a/sample/src/main/java/eu/inloop/viewmodel/sample/viewmodel/view/IPageView.java +++ b/sample/src/main/java/eu/inloop/viewmodel/sample/viewmodel/view/IPageView.java @@ -1,6 +1,11 @@ package eu.inloop.viewmodel.sample.viewmodel.view; import eu.inloop.viewmodel.IView; +import eu.inloop.viewmodel.binding.ViewModelBindingConfig; public class IPageView implements IView { + @Override + public ViewModelBindingConfig getViewModelBindingConfig() { + return null; + } } diff --git a/sample/src/main/java/eu/inloop/viewmodel/sample/viewmodel/view/ISampleBindingView.java b/sample/src/main/java/eu/inloop/viewmodel/sample/viewmodel/view/ISampleBindingView.java new file mode 100644 index 0000000..983c325 --- /dev/null +++ b/sample/src/main/java/eu/inloop/viewmodel/sample/viewmodel/view/ISampleBindingView.java @@ -0,0 +1,10 @@ +package eu.inloop.viewmodel.sample.viewmodel.view; + +import eu.inloop.viewmodel.IView; + +/** + * Created by stepansanda on 21/11/2016. + */ + +public interface ISampleBindingView extends IView { +} diff --git a/sample/src/main/res/layout/activity_sample_binding.xml b/sample/src/main/res/layout/activity_sample_binding.xml new file mode 100644 index 0000000..81d46df --- /dev/null +++ b/sample/src/main/res/layout/activity_sample_binding.xml @@ -0,0 +1,7 @@ + diff --git a/sample/src/main/res/layout/fragment_sample_binding.xml b/sample/src/main/res/layout/fragment_sample_binding.xml new file mode 100644 index 0000000..05b0828 --- /dev/null +++ b/sample/src/main/res/layout/fragment_sample_binding.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + +