package com.google.samples.apps.iosched.archframework; import android.os.Bundle; import android.support.annotation.Nullable; import static com.google.samples.apps.iosched.util.LogUtils.LOGE; import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; /** * This implements the {@link Presenter} interface. This Presenter can interact with more than one * {@link UpdatableView}, based on the constructor used to create it. The most common use case for * have multiple {@link UpdatableView} controlled by the same presenter is an Activity with tabs, * where each view in the tab (typically a {@link android.app.Fragment}) is an {@link * UpdatableView}. * <p/> * It requests the model to load its initial data, it listens to events from the {@link * UpdatableView}(s) and passes the user actions on to the Model, then it updates the {@link * UpdatableView}(s) once the Model has completed its data update. */ public class PresenterImpl implements Presenter, UpdatableView.UserActionListener { private static final String TAG = makeLogTag(PresenterImpl.class); /** * The UI that this Presenter controls. */ @Nullable private UpdatableView[] mUpdatableViews; /** * The Model that this Presenter controls. */ private Model mModel; /** * The queries to load when the {@link android.app.Activity} loading this {@link * android.app.Fragment} is created. */ private QueryEnum[] mInitialQueriesToLoad; /** * The actions allowed by the presenter. */ private UserActionEnum[] mValidUserActions; /** * Use this constructor if this Presenter controls one view only. */ public PresenterImpl(Model model, UpdatableView view, UserActionEnum[] validUserActions, QueryEnum[] initialQueries) { this(model, new UpdatableView[]{view}, validUserActions, initialQueries); } /** * Use this constructor if this Presenter controls more than one view. */ public PresenterImpl(Model model, @Nullable UpdatableView[] views, UserActionEnum[] validUserActions, QueryEnum[] initialQueries) { mModel = model; if (views != null) { mUpdatableViews = views; for (int i = 0; i < mUpdatableViews.length; i++) { mUpdatableViews[i].addListener(this); } } else { LOGE(TAG, "Creating a PresenterImpl with null View"); } mValidUserActions = validUserActions; mInitialQueriesToLoad = initialQueries; } @Override public void loadInitialQueries() { // Load data queries if any. if (mInitialQueriesToLoad != null && mInitialQueriesToLoad.length > 0) { for (int i = 0; i < mInitialQueriesToLoad.length; i++) { mModel.requestData(mInitialQueriesToLoad[i], new Model.DataQueryCallback() { @Override public void onModelUpdated(Model model, QueryEnum query) { if (mUpdatableViews != null) { for (int i = 0; i < mUpdatableViews.length; i++) { mUpdatableViews[i].displayData(model, query); } } else { LOGE(TAG, "loadInitialQueries(), cannot notify a null view!"); } } @Override public void onError(QueryEnum query) { if (mUpdatableViews != null) { for (int i = 0; i < mUpdatableViews.length; i++) { mUpdatableViews[i].displayErrorMessage(query); } } else { LOGE(TAG, "loadInitialQueries(), cannot notify a null view!"); } } }); } } else { // No data query to load, update the view. if (mUpdatableViews != null) { for (int i = 0; i < mUpdatableViews.length; i++) { mUpdatableViews[i].displayData(mModel, null); } } else { LOGE(TAG, "loadInitialQueries(), cannot notify a null view!"); } } } /** * Called when the user has performed an {@code action}, with data to be passed as a {@link * android.os.Bundle} in {@code args}. * <p/> * Add the constants used to store values in the bundle to the Model implementation class as * final static protected strings. * <p/> * If the {@code action} should trigger a new data query, specify the query ID by storing the * associated Integer in the {@code args} using {@link ModelWithLoaderManager#KEY_RUN_QUERY_ID}. * The {@code args} will be passed on to the cursor loader so you can pass in extra arguments * for your query. */ @Override public void onUserAction(UserActionEnum action, @Nullable Bundle args) { boolean isValid = false; if (mValidUserActions != null && action != null) { for (int i = 0; i < mValidUserActions.length; i++) { if (mValidUserActions[i].getId() == action.getId()) { isValid = true; } } } if (isValid) { mModel.deliverUserAction(action, args, new Model.UserActionCallback() { @Override public void onModelUpdated(Model model, UserActionEnum userAction) { if (mUpdatableViews != null) { for (int i = 0; i < mUpdatableViews.length; i++) { mUpdatableViews[i].displayUserActionResult(model, userAction, true); } } else { LOGE(TAG, "onUserAction(), cannot notify a null view!"); } } @Override public void onError(UserActionEnum userAction) { if (mUpdatableViews != null) { for (int i = 0; i < mUpdatableViews.length; i++) { mUpdatableViews[i].displayUserActionResult(null, userAction, false); } // User action not understood by model, even though the presenter understands // it. LOGE(TAG, "Model doesn't implement user action " + userAction.getId() + ". Have you forgotten to implement this UserActionEnum in your " + "model," + " or have you called setValidUserActions on your presenter with a " + "UserActionEnum that it shouldn't support?"); } else { LOGE(TAG, "onUserAction(), cannot notify a null view!"); } } }); } else { if (mUpdatableViews != null) { for (int i = 0; i < mUpdatableViews.length; i++) { mUpdatableViews[i].displayUserActionResult(null, action, false); } // User action not understood. throw new RuntimeException( "Invalid user action " + (action != null ? action.getId() : null) + ". Have you called setValidUserActions on your presenter, with all " + "the UserActionEnum you want to support?"); } else { LOGE(TAG, "onUserAction(), cannot notify a null view!"); } } } }