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 final class FragmentViewModelManager {
private static final String VIEW_MODEL_ID_KEY = "fragment_view_model_id";
private static final String VIEW_MODEL_STATE_KEY = "fragment_view_model_state";
private static final FragmentViewModelManager instance = new FragmentViewModelManager();
private Map<String, FragmentViewModel> viewModels = new HashMap<>();
public static @NonNull FragmentViewModelManager getInstance() {
return instance;
}
@SuppressWarnings("unchecked")
public <T extends FragmentViewModel> T fetch(final @NonNull Context context, final @NonNull Class<T> viewModelClass,
final @Nullable Bundle savedInstanceState) {
final String id = fetchId(savedInstanceState);
FragmentViewModel viewModel = viewModels.get(id);
if (viewModel == null) {
viewModel = create(context, viewModelClass, savedInstanceState, id);
}
return (T) viewModel;
}
public void destroy(final @NonNull FragmentViewModel viewModel) {
viewModel.onDestroy();
final Iterator<Map.Entry<String, FragmentViewModel>> iterator = viewModels.entrySet().iterator();
while (iterator.hasNext()) {
final Map.Entry<String, FragmentViewModel> entry = iterator.next();
if (viewModel.equals(entry.getValue())) {
iterator.remove();
}
}
}
public void save(final @NonNull FragmentViewModel viewModel, final @NonNull Bundle envelope) {
envelope.putString(VIEW_MODEL_ID_KEY, findIdForViewModel(viewModel));
final Bundle state = new Bundle();
envelope.putBundle(VIEW_MODEL_STATE_KEY, state);
}
private <T extends FragmentViewModel> FragmentViewModel 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 FragmentViewModel viewModel;
try {
final Constructor constructor = viewModelClass.getConstructor(Environment.class);
viewModel = (FragmentViewModel) 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, viewModel);
viewModel.onCreate(context, BundleUtils.maybeGetBundle(savedInstanceState, VIEW_MODEL_STATE_KEY));
return viewModel;
}
private String fetchId(final @Nullable Bundle savedInstanceState) {
return savedInstanceState != null ?
savedInstanceState.getString(VIEW_MODEL_ID_KEY) :
UUID.randomUUID().toString();
}
private String findIdForViewModel(final @NonNull FragmentViewModel viewModel) {
for (final Map.Entry<String, FragmentViewModel> entry : viewModels.entrySet()) {
if (viewModel.equals(entry.getValue())) {
return entry.getKey();
}
}
throw new RuntimeException("Cannot find view model in map!");
}
}