1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.lifecycle; 18 19 import android.app.Application; 20 21 import androidx.annotation.MainThread; 22 import androidx.annotation.NonNull; 23 24 import java.lang.reflect.InvocationTargetException; 25 26 /** 27 * An utility class that provides {@code ViewModels} for a scope. 28 * <p> 29 * Default {@code ViewModelProvider} for an {@code Activity} or a {@code Fragment} can be obtained 30 * from {@link androidx.lifecycle.ViewModelProviders} class. 31 */ 32 @SuppressWarnings("WeakerAccess") 33 public class ViewModelProvider { 34 35 private static final String DEFAULT_KEY = 36 "androidx.lifecycle.ViewModelProvider.DefaultKey"; 37 38 /** 39 * Implementations of {@code Factory} interface are responsible to instantiate ViewModels. 40 */ 41 public interface Factory { 42 /** 43 * Creates a new instance of the given {@code Class}. 44 * <p> 45 * 46 * @param modelClass a {@code Class} whose instance is requested 47 * @param <T> The type parameter for the ViewModel. 48 * @return a newly created ViewModel 49 */ 50 @NonNull create(@onNull Class<T> modelClass)51 <T extends ViewModel> T create(@NonNull Class<T> modelClass); 52 } 53 54 private final Factory mFactory; 55 private final ViewModelStore mViewModelStore; 56 57 /** 58 * Creates {@code ViewModelProvider}, which will create {@code ViewModels} via the given 59 * {@code Factory} and retain them in a store of the given {@code ViewModelStoreOwner}. 60 * 61 * @param owner a {@code ViewModelStoreOwner} whose {@link ViewModelStore} will be used to 62 * retain {@code ViewModels} 63 * @param factory a {@code Factory} which will be used to instantiate 64 * new {@code ViewModels} 65 */ ViewModelProvider(@onNull ViewModelStoreOwner owner, @NonNull Factory factory)66 public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) { 67 this(owner.getViewModelStore(), factory); 68 } 69 70 /** 71 * Creates {@code ViewModelProvider}, which will create {@code ViewModels} via the given 72 * {@code Factory} and retain them in the given {@code store}. 73 * 74 * @param store {@code ViewModelStore} where ViewModels will be stored. 75 * @param factory factory a {@code Factory} which will be used to instantiate 76 * new {@code ViewModels} 77 */ ViewModelProvider(@onNull ViewModelStore store, @NonNull Factory factory)78 public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) { 79 mFactory = factory; 80 this.mViewModelStore = store; 81 } 82 83 /** 84 * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or 85 * an activity), associated with this {@code ViewModelProvider}. 86 * <p> 87 * The created ViewModel is associated with the given scope and will be retained 88 * as long as the scope is alive (e.g. if it is an activity, until it is 89 * finished or process is killed). 90 * 91 * @param modelClass The class of the ViewModel to create an instance of it if it is not 92 * present. 93 * @param <T> The type parameter for the ViewModel. 94 * @return A ViewModel that is an instance of the given type {@code T}. 95 */ 96 @NonNull 97 @MainThread get(@onNull Class<T> modelClass)98 public <T extends ViewModel> T get(@NonNull Class<T> modelClass) { 99 String canonicalName = modelClass.getCanonicalName(); 100 if (canonicalName == null) { 101 throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels"); 102 } 103 return get(DEFAULT_KEY + ":" + canonicalName, modelClass); 104 } 105 106 /** 107 * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or 108 * an activity), associated with this {@code ViewModelProvider}. 109 * <p> 110 * The created ViewModel is associated with the given scope and will be retained 111 * as long as the scope is alive (e.g. if it is an activity, until it is 112 * finished or process is killed). 113 * 114 * @param key The key to use to identify the ViewModel. 115 * @param modelClass The class of the ViewModel to create an instance of it if it is not 116 * present. 117 * @param <T> The type parameter for the ViewModel. 118 * @return A ViewModel that is an instance of the given type {@code T}. 119 */ 120 @NonNull 121 @MainThread get(@onNull String key, @NonNull Class<T> modelClass)122 public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) { 123 ViewModel viewModel = mViewModelStore.get(key); 124 125 if (modelClass.isInstance(viewModel)) { 126 //noinspection unchecked 127 return (T) viewModel; 128 } else { 129 //noinspection StatementWithEmptyBody 130 if (viewModel != null) { 131 // TODO: log a warning. 132 } 133 } 134 135 viewModel = mFactory.create(modelClass); 136 mViewModelStore.put(key, viewModel); 137 //noinspection unchecked 138 return (T) viewModel; 139 } 140 141 /** 142 * Simple factory, which calls empty constructor on the give class. 143 */ 144 public static class NewInstanceFactory implements Factory { 145 146 @SuppressWarnings("ClassNewInstance") 147 @NonNull 148 @Override create(@onNull Class<T> modelClass)149 public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { 150 //noinspection TryWithIdenticalCatches 151 try { 152 return modelClass.newInstance(); 153 } catch (InstantiationException e) { 154 throw new RuntimeException("Cannot create an instance of " + modelClass, e); 155 } catch (IllegalAccessException e) { 156 throw new RuntimeException("Cannot create an instance of " + modelClass, e); 157 } 158 } 159 } 160 161 /** 162 * {@link Factory} which may create {@link AndroidViewModel} and 163 * {@link ViewModel}, which have an empty constructor. 164 */ 165 public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory { 166 167 private static AndroidViewModelFactory sInstance; 168 169 /** 170 * Retrieve a singleton instance of AndroidViewModelFactory. 171 * 172 * @param application an application to pass in {@link AndroidViewModel} 173 * @return A valid {@link AndroidViewModelFactory} 174 */ 175 @NonNull getInstance(@onNull Application application)176 public static AndroidViewModelFactory getInstance(@NonNull Application application) { 177 if (sInstance == null) { 178 sInstance = new AndroidViewModelFactory(application); 179 } 180 return sInstance; 181 } 182 183 private Application mApplication; 184 185 /** 186 * Creates a {@code AndroidViewModelFactory} 187 * 188 * @param application an application to pass in {@link AndroidViewModel} 189 */ AndroidViewModelFactory(@onNull Application application)190 public AndroidViewModelFactory(@NonNull Application application) { 191 mApplication = application; 192 } 193 194 @NonNull 195 @Override create(@onNull Class<T> modelClass)196 public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { 197 if (AndroidViewModel.class.isAssignableFrom(modelClass)) { 198 //noinspection TryWithIdenticalCatches 199 try { 200 return modelClass.getConstructor(Application.class).newInstance(mApplication); 201 } catch (NoSuchMethodException e) { 202 throw new RuntimeException("Cannot create an instance of " + modelClass, e); 203 } catch (IllegalAccessException e) { 204 throw new RuntimeException("Cannot create an instance of " + modelClass, e); 205 } catch (InstantiationException e) { 206 throw new RuntimeException("Cannot create an instance of " + modelClass, e); 207 } catch (InvocationTargetException e) { 208 throw new RuntimeException("Cannot create an instance of " + modelClass, e); 209 } 210 } 211 return super.create(modelClass); 212 } 213 } 214 } 215