Home | History | Annotate | Download | only in lifecycle
      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
     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      */
     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      */
     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
     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
    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
    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
    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          */
    190         public AndroidViewModelFactory(@NonNull Application application) {
    191             mApplication = application;
    192         }
    193 
    194         @NonNull
    195         @Override
    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