Skip to content

Commit a5bd061

Browse files
author
Daniel Novak
committed
Model cache lookup speed improved
1 parent eebc111 commit a5bd061

File tree

4 files changed

+41
-43
lines changed

4 files changed

+41
-43
lines changed

README.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,21 @@ AndroidViewModel
33

44
Separating data and state handling from Fragments or Activities without lots of boilerplate-code. Reducing them to simple <i>dumb views</i>.
55

6-
<b>Basic idea behind this library</b>
7-
8-
An instance of a Model class is assigned to your Fragment or Activity during the first creation and is kept during it's life cycle, even between display orientation changes. The Model instance is removed after the Fragment or Activity is completely gone (finished, poppped from backstack, replaced without keeping it in backstack).
6+
<b>Basic idea behind this library</b>.
7+
An instance of a Model class is assigned to your Fragment or Activity during the first creation and is kept during it's life cycle, even between display orientation changes. The Model instance is removed after the Fragment or Activity is completely gone (finished, popped from backstack, replaced without keeping it in backstack).
98

109
You can execute asynchronous tasks in this Model instance and this class is not destroyed during orientation change. All data handling and state logic should be placed inside this class. The Fragment or Activity is just a "dumb" view.
1110

12-
<b>How does it work?</b> </p>
13-
A unique global ID is generated for the first time your Fragment or Activity is shown. This ID is passed on during orientation changes. Openning another instance of the same Fragment or Activity will result in a different ID. The ID is unique screen identifier. A ViewModel class is created and bound to this ID. The corresponding ViewModel instance is attached to your Fragment or Activity after an orientation change or if you return to the fragment in the back stack.
14-
The ViewModel is discarded once the Fragment/Activity is not reachaeable anymore (activity is finished or fragment permanently removed).
11+
<b>How does it work?</b>
12+
A unique global ID is generated for the first time your Fragment or Activity is shown. This ID is passed on during orientation changes. Opening another instance of the same Fragment or Activity will result in a different ID. The ID is unique screen identifier. A ViewModel class is created and bound to this ID. The corresponding ViewModel instance is attached to your Fragment or Activity after an orientation change or if you return to the fragment in the back stack.
13+
The ViewModel is discarded once the Fragment/Activity is not reachable anymore (activity is finished or fragment permanently removed).
14+
15+
<b>Why no controller layer?</b>
16+
This is not a strict MVC/MVP architecture - simply because we felt that having another layer between the "model" and the view does not bring enough advantages. So to further reduce the code this was simplified, where the Model is talking to the View over an interface. In mobile application most of the code is about interaction with the UI (getting data from API/DB, showing the data, manipulating, saving) so a more direct connection between the layers felt appropriate.
1517

1618
<b>Sample Workflow</b>:
1719

18-
1. <small>Fragment is show to user. A ViewModel is assigned.</small>
20+
1. <small>Fragment is shown to user. A ViewModel is assigned.</small>
1921
2. Fragment notifies the View that it's ready.
2022
3. ViewModel starts the async task to load data. Tells the view to show progress.
2123
4. User rotates the display. The ViewModel continues with the loading part.
@@ -36,7 +38,7 @@ or Maven:
3638
<dependency>
3739
<groupId>eu.inloop</groupId>
3840
<artifactId>androidviewmodel</artifactId>
39-
<version>0.1</version>
41+
<version>0.2</version>
4042
</dependency>
4143
```
4244

library/src/main/java/eu/inloop/viewmodel/AbstractViewModel.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@
88

99
public abstract class AbstractViewModel<T extends IView> {
1010

11-
@NonNull
12-
private String mUniqueIdentifier;
11+
private int mUniqueIdentifier;
1312

1413
@Nullable
1514
private T mView;
1615

17-
void setUniqueIdentifier(String uniqueIdentifier) {
16+
void setUniqueIdentifier(int uniqueIdentifier) {
1817
mUniqueIdentifier = uniqueIdentifier;
1918
}
2019

@@ -23,7 +22,7 @@ void setUniqueIdentifier(String uniqueIdentifier) {
2322
* @return An app unique identifier for the current viewmodel instance (will be kept during orientation
2423
* change). This identifier will be reset in case the corresponding activity is killed.
2524
*/
26-
public String getUniqueIdentifier() {
25+
public int getUniqueIdentifier() {
2726
return mUniqueIdentifier;
2827
}
2928

library/src/main/java/eu/inloop/viewmodel/ViewModelHelper.java

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@
55
import android.support.annotation.NonNull;
66
import android.support.annotation.Nullable;
77
import android.support.v4.app.Fragment;
8-
import android.support.v4.app.FragmentManager;
98
import android.util.Log;
109

11-
import java.util.UUID;
10+
import java.util.concurrent.atomic.AtomicInteger;
1211

1312
public class ViewModelHelper<T extends IView, R extends AbstractViewModel<T>> {
1413

15-
private String mScreenId;
14+
private int mScreenId;
1615
private R mViewModel;
1716
private boolean mModelRemoved;
1817
private boolean mOnSaveInstanceCalled;
18+
private static AtomicInteger sModelIndex = new AtomicInteger(0);
1919

2020
/**
2121
* Call from {@link android.app.Activity#onCreate(android.os.Bundle)} or
@@ -33,9 +33,9 @@ public void onCreate(@Nullable Bundle savedInstanceState,
3333

3434
// screen (activity/fragment) created for first time, attach unique ID
3535
if (savedInstanceState == null) {
36-
mScreenId = UUID.randomUUID().toString();
36+
mScreenId = sModelIndex.incrementAndGet();
3737
} else {
38-
mScreenId = savedInstanceState.getString("identifier");
38+
mScreenId = savedInstanceState.getInt("identifier");
3939
mOnSaveInstanceCalled = false;
4040
}
4141

@@ -151,21 +151,18 @@ public R getViewModel() {
151151
* @param bundle
152152
*/
153153
public void onSaveInstanceState(@NonNull Bundle bundle) {
154-
bundle.putString("identifier", mScreenId);
154+
bundle.putInt("identifier", mScreenId);
155155
if (mViewModel != null) {
156156
mViewModel.saveState(bundle);
157157
mOnSaveInstanceCalled = true;
158158
}
159159
}
160160

161-
private boolean removeViewModel() {
161+
private void removeViewModel() {
162162
if (!mModelRemoved) {
163-
boolean removed = ViewModelProvider.getInstance().remove(mScreenId);
163+
ViewModelProvider.getInstance().remove(mScreenId);
164164
mViewModel.onModelRemoved();
165165
mModelRemoved = true;
166-
return removed;
167-
} else {
168-
return false;
169166
}
170167
}
171168
}
Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package eu.inloop.viewmodel;
22
import android.support.annotation.NonNull;
3+
import android.util.SparseArray;
34

45
import java.util.HashMap;
56

@@ -13,54 +14,53 @@ public class ViewModelProvider {
1314

1415
private static final ViewModelProvider sInstance = new ViewModelProvider();
1516

16-
private final HashMap<String, AbstractViewModel<? extends IView>> mViewModelCache;
17+
private final SparseArray<AbstractViewModel<? extends IView>> mViewModelCache;
1718

1819
private ViewModelProvider() {
19-
mViewModelCache = new HashMap<>();
20+
mViewModelCache = new SparseArray<>();
2021
}
2122

2223
public static ViewModelProvider getInstance() {
2324
return sInstance;
2425
}
2526

26-
public boolean remove(@NonNull String key) {
27-
return mViewModelCache.remove(key) != null;
28-
}
29-
30-
public static class ViewModelWrapper<T extends IView> {
31-
@NonNull
32-
public final AbstractViewModel<T> viewModel;
33-
public final boolean wasCreated;
34-
35-
private ViewModelWrapper(@NonNull AbstractViewModel<T> mViewModel, boolean mWasCreated) {
36-
this.viewModel = mViewModel;
37-
this.wasCreated = mWasCreated;
38-
}
27+
public synchronized void remove(int modelIndex) {
28+
mViewModelCache.remove(modelIndex);
3929
}
4030

4131
/**
4232
* Call this in {@link android.app.Activity#onStop()} if {@link android.app.Activity#isFinishing()}
4333
*/
44-
public void removeAllViewModels() {
34+
public synchronized void removeAllViewModels() {
4535
mViewModelCache.clear();
4636
}
4737

4838
@SuppressWarnings("unchecked")
4939
@NonNull
50-
public synchronized <T extends IView> ViewModelWrapper<T> getViewModel(@NonNull String key, @NonNull Class<? extends AbstractViewModel<T>> viewModelClass) {
51-
AbstractViewModel<T> instance = (AbstractViewModel<T>) mViewModelCache.get(key);
40+
public synchronized <T extends IView> ViewModelWrapper<T> getViewModel(int modelIndex, @NonNull Class<? extends AbstractViewModel<T>> viewModelClass) {
41+
AbstractViewModel<T> instance = (AbstractViewModel<T>) mViewModelCache.get(modelIndex);
5242
if (instance != null) {
5343
return new ViewModelWrapper<>(instance, false);
5444
}
5545

5646
try {
5747
instance = viewModelClass.newInstance();
58-
instance.setUniqueIdentifier(key);
59-
mViewModelCache.put(key, instance);
48+
instance.setUniqueIdentifier(modelIndex);
49+
mViewModelCache.put(modelIndex, instance);
6050
return new ViewModelWrapper<>(instance, true);
6151
} catch (InstantiationException | IllegalAccessException e) {
6252
throw new RuntimeException(e);
6353
}
6454
}
6555

56+
public static class ViewModelWrapper<T extends IView> {
57+
@NonNull
58+
public final AbstractViewModel<T> viewModel;
59+
public final boolean wasCreated;
60+
61+
private ViewModelWrapper(@NonNull AbstractViewModel<T> mViewModel, boolean mWasCreated) {
62+
this.viewModel = mViewModel;
63+
this.wasCreated = mWasCreated;
64+
}
65+
}
6666
}

0 commit comments

Comments
 (0)