package com.kickstarter.libs; import android.content.Context; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.kickstarter.KSApplication; import com.kickstarter.libs.utils.BundleUtils; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.UUID; public class ActivityViewModelManager { private static final String VIEW_MODEL_ID_KEY = "view_model_id"; private static final String VIEW_MODEL_STATE_KEY = "view_model_state"; private static final ActivityViewModelManager instance = new ActivityViewModelManager(); private Map<String, ActivityViewModel> viewModels = new HashMap<>(); public static @NonNull ActivityViewModelManager getInstance() { return instance; } @SuppressWarnings("unchecked") public <T extends ActivityViewModel> T fetch(final @NonNull Context context, final @NonNull Class<T> viewModelClass, final @Nullable Bundle savedInstanceState) { final String id = fetchId(savedInstanceState); ActivityViewModel activityViewModel = viewModels.get(id); if (activityViewModel == null) { activityViewModel = create(context, viewModelClass, savedInstanceState, id); } return (T) activityViewModel; } public void destroy(final @NonNull ActivityViewModel activityViewModel) { activityViewModel.onDestroy(); final Iterator<Map.Entry<String, ActivityViewModel>> iterator = viewModels.entrySet().iterator(); while (iterator.hasNext()) { final Map.Entry<String, ActivityViewModel> entry = iterator.next(); if (activityViewModel.equals(entry.getValue())) { iterator.remove(); } } } public void save(final @NonNull ActivityViewModel activityViewModel, final @NonNull Bundle envelope) { envelope.putString(VIEW_MODEL_ID_KEY, findIdForViewModel(activityViewModel)); final Bundle state = new Bundle(); envelope.putBundle(VIEW_MODEL_STATE_KEY, state); } private <T extends ActivityViewModel> ActivityViewModel create(final @NonNull Context context, final @NonNull Class<T> viewModelClass, final @Nullable Bundle savedInstanceState, final @NonNull String id) { final KSApplication application = (KSApplication) context.getApplicationContext(); final Environment environment = application.component().environment(); final ActivityViewModel activityViewModel; try { final Constructor constructor = viewModelClass.getConstructor(Environment.class); activityViewModel = (ActivityViewModel) constructor.newInstance(environment); // Need to catch these exceptions separately, otherwise the compiler turns them into `ReflectiveOperationException`. // That exception is only available in API19+ } catch (IllegalAccessException exception) { throw new RuntimeException(exception); } catch (InvocationTargetException exception) { throw new RuntimeException(exception); } catch (InstantiationException exception) { throw new RuntimeException(exception); } catch (NoSuchMethodException exception) { throw new RuntimeException(exception); } viewModels.put(id, activityViewModel); activityViewModel.onCreate(context, BundleUtils.maybeGetBundle(savedInstanceState, VIEW_MODEL_STATE_KEY)); return activityViewModel; } private String fetchId(final @Nullable Bundle savedInstanceState) { return savedInstanceState != null ? savedInstanceState.getString(VIEW_MODEL_ID_KEY) : UUID.randomUUID().toString(); } private String findIdForViewModel(final @NonNull ActivityViewModel activityViewModel) { for (final Map.Entry<String, ActivityViewModel> entry : viewModels.entrySet()) { if (activityViewModel.equals(entry.getValue())) { return entry.getKey(); } } throw new RuntimeException("Cannot find view model in map!"); } }