Java tutorial
/* * ================================================================================================= * Copyright (C) 2014 Martin Albedinsky * ================================================================================================= * Licensed under the Apache License, Version 2.0 or later (further "License" only). * ------------------------------------------------------------------------------------------------- * You may use this file only in compliance with the License. More details and copy of this License * you may obtain at * * http://www.apache.org/licenses/LICENSE-2.0 * * You can redistribute, modify or publish any part of the code written within this file but as it * is described in the License, the software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES or CONDITIONS OF ANY KIND. * * See the License for the specific language governing permissions and limitations under the License. * ================================================================================================= */ package com.albedinsky.android.support.intent; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.support.annotation.AnimRes; import android.support.annotation.CheckResult; import android.support.annotation.IntRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.StringRes; import android.support.v4.app.Fragment; import android.util.AndroidRuntimeException; import android.widget.Toast; import com.albedinsky.android.support.intent.internal.IntentActivityWrapper; import com.albedinsky.android.support.intent.internal.IntentFragmentWrapper; import com.albedinsky.android.support.intent.internal.IntentContextWrapper; /** * A BaseIntent specifies base API for intent builders. Some Android intents require a lots of data * to be supplied to them and the intent builders should provide API to simplify such a set up process. * <p> * A specific implementation of BaseIntent builder is designed primarily for usage within {@link Activity} * or {@link Fragment} context: * <ul> * <li>{@link #BaseIntent(Activity)}</li> * <li>{@link #BaseIntent(Fragment)}</li> * </ul> * Each of these contexts is wrapped into {@link IntentContextWrapper} that simplifies and unifies * accessing all necessary data and logic provided by both these contexts. * * <h3>Implementation</h3> * Implementations of the BaseIntent class need to implement these methods: * <ul> * <li> * {@link #build()} * <p> * This method is called whenever {@link #start()} is invoked and a specific BaseIntent builder * implementation should build here an intent which is for that type of intent builder specific * and populate it with a data supplied from outside through its setters API. * </li> * <li> * {@link #onStart(Intent)} * <p> * It is not required to override this method. Default implementation of this method only starts * the intent obtained via {@link #build()} method using {@link #startActivity(Intent)}. * If a specific BaseIntent implementation requires for example starting intent as activity for result, * than it should be done within this method. * </li> * </ul> * * <h3>Activity transitions</h3> * BaseIntent class supports also API for supplying of activity transitions via {@link #enterTransition(int)} * and {@link #exitTransition(int)}. These transitions will be used whenever {@link #startActivity(Intent)} * or {@link #startActivityForResult(Intent, int)} is called. * * @param <I> A type of the BaseIntent builder implementation to allow proper setter methods chaining. * @author Martin Albedinsky */ public abstract class BaseIntent<I extends BaseIntent> { /** * Interface =================================================================================== */ /** * Constants =================================================================================== */ /** * Log TAG. */ // private static final String TAG = "BaseIntent"; /** * Static members ============================================================================== */ /** * Members ===================================================================================== */ /** * Title text for the activity chooser dialog. */ CharSequence mDialogTitle = "Choose"; /** * Wrapper for an intent's context. */ IntentContextWrapper mContextWrapper; /** * Message text for the toast, in case, when there is no activity to process requested intent. */ private CharSequence mActivityNotFoundMessage = "No application found to handle this action"; /** * Window transition resource id. */ private int mEnterTransition, mExitTransition; /** * Flag indicating whether to apply window transition or not. */ private boolean mApplyTransitions; /** * Constructors ================================================================================ */ /** * Creates a new instance of BaseIntent representation for the given activity context. Note, that * this activity receives result in the {@link Activity#onActivityResult(int, int, Intent)} * in case, that this intent was started for some result. * * @param activity Activity context. */ public BaseIntent(@NonNull Activity activity) { this(new IntentActivityWrapper(activity)); } /** * Creates a new instance of BaseIntent representation for the given fragment context. Note, that * this fragment receives result in the {@link android.support.v4.app.Fragment#onActivityResult(int, int, Intent)} * in case, that this intent was started for some result. * * @param fragment Fragment context. */ public BaseIntent(@NonNull Fragment fragment) { this(new IntentFragmentWrapper(fragment)); } /** * Creates a new instance of BaseIntent representation for the given context wrapper. * * @param contextWrapper Context wrapper used to access the current context data. */ private BaseIntent(IntentContextWrapper contextWrapper) { this.mContextWrapper = contextWrapper; this.mDialogTitle = contextWrapper.getString(R.string.intent_chooser_dialog_title); } /** * Methods ===================================================================================== */ /** * Same as {@link #dialogTitle(CharSequence)} for resource id. * * @param resId The resource if of desired title text. */ public I dialogTitle(@StringRes int resId) { return dialogTitle(obtainText(resId)); } /** * Sets a title for the chooser dialog. * <p> * Default value: <b>Choose</b> * * @param title The desired title text. * @return This intent builder to allow methods chaining. * @see #dialogTitle() */ @SuppressWarnings("unchecked") public I dialogTitle(@Nullable CharSequence title) { this.mDialogTitle = title != null ? title : ""; return (I) this; } /** * Returns the title text for the chooser dialog. * <p> * Default value: <b>Choose</b> * * @return Title text. */ @NonNull public CharSequence dialogTitle() { return mDialogTitle != null ? mDialogTitle : ""; } /** * Same as {@link #activityNotFoundMessage(CharSequence)} for resource id. * * @param resId Resource id of the desired message text. */ public I activityNotFoundMessage(@StringRes int resId) { return activityNotFoundMessage(obtainText(resId)); } /** * Sets a message for the toast that is showed in case, when there wasn't found any activity * to handle started intent. * * @param message The desired message text. * @return This intent builder to allow methods chaining. * @see #activityNotFoundMessage() */ @SuppressWarnings("unchecked") public I activityNotFoundMessage(@Nullable CharSequence message) { this.mActivityNotFoundMessage = message; return (I) this; } /** * Returns the message text for the toast that is showed in case, when there wasn't found any * activity to handle started intent. * * @return Message text. * @see #activityNotFoundMessage(CharSequence) */ @Nullable public CharSequence activityNotFoundMessage() { return mActivityNotFoundMessage; } /** * Returns the context wrapper of this intent builder. * * @return One of {@link IntentActivityWrapper}, * {@link IntentFragmentWrapper}. */ @NonNull public final IntentContextWrapper contextWrapper() { return mContextWrapper; } /** * Sets a transition to be used when starting intent to override default pending window enter * transition. See {@link Activity#overridePendingTransition(int, int)} for more info. * * @param transition Resource id of the desired window transition (animation). * @return This intent builder to allow methods chaining. */ public I enterTransition(@AnimRes int transition) { return transitions(transition, mExitTransition); } /** * Returns the enter transition that should be used when starting intent to override default * pending window enter transition. * * @return Resource id of the enter transition or {@code 0} if there was no transition set yet. * @see #enterTransition(int) */ @AnimRes public int enterTransition() { return mEnterTransition; } /** * Sets a transition to be used when starting intent to override default pending window exit * transition. See {@link Activity#overridePendingTransition(int, int)} for more info. * * @param transition Resource id of the desired window transition (animation). * @return This intent builder to allow methods chaining. */ public I exitTransition(@AnimRes int transition) { return transitions(mEnterTransition, transition); } /** * Returns the exit transition that should be used when starting intent to override default * pending window exit transition. * * @return Resource id of the exit transition or {@code 0} if there was no transition set yet. * @see #exitTransition(int) (int) */ @AnimRes public int exitTransition() { return mExitTransition; } /** * Sets both, enter + exit, transitions to be used when starting intent to override default pending * window transitions. See {@link Activity#overridePendingTransition(int, int)} for more info. * * @param enterTransition Resource id of the desired window enter transition (animation). * @param exitTransition Resource id of the desired window exit transition (animation). * @return This intent builder to allow methods chaining. */ @SuppressWarnings("unchecked") public I transitions(@AnimRes int enterTransition, @AnimRes int exitTransition) { if (enterTransition >= 0 || exitTransition >= 0) { this.mEnterTransition = enterTransition; this.mExitTransition = exitTransition; this.mApplyTransitions = true; } else { this.mApplyTransitions = false; } return (I) this; } /** * Obtains a string from the attached context. * * @param resId Resource id of the desired string. * @return Obtained string from application resources. */ @NonNull protected final String obtainString(@StringRes int resId) { return resId != 0 ? mContextWrapper.getString(resId) : ""; } /** * Obtains a text from the attached context. * * @param resId Resource id of the desired text. * @return Obtained text from application resources. */ @NonNull protected final CharSequence obtainText(@StringRes int resId) { return resId != 0 ? mContextWrapper.getText(resId) : ""; } /** * Starts an instance of intent created by {@link #build()}. * <p> * <b>Note</b>, that if there is no activity found to handle created intent, {@link #notifyActivityNotFound()} * will be invoked from here and {@code false} will be returned. * * @return {@code True} if intent was started, {@code false} otherwise. * @throws NullPointerException If the attached context is already invalid. */ public boolean start() { if (mContextWrapper.getContext() == null) { throw new NullPointerException("Context is already invalid."); } final Intent intent = build(); if (hasActivityForIntent(intent)) { return onStart(intent); } notifyActivityNotFound(); return false; } /** * Called to create an instance of {@link Intent} from the current data of this * intent builder implementation. * * @return An instance of Intent specific for this intent builder. * @throws AndroidRuntimeException If this builder does not have all necessary data to build the * requested intent. */ @NonNull public Intent build() { ensureCanBuildOrThrow(); return onBuild(); } /** * Called to ensure that this builder can build its Intent from its current data. If not an * exception create via {@link #cannotBuildIntentException(String)} can be thrown. */ protected void ensureCanBuildOrThrow() { } /** * Invoked from {@link #build()} after {@link #ensureCanBuildOrThrow()} to build * intent specific for this intent builder from the current data. * * @return Intent instance with current data. */ protected abstract Intent onBuild(); /** * Checks whether there is any activity that can handle the specified <var>intent</var>. * * @param intent The intent for which to resolve activity. * @return {@code True} if activity for the intent has been resolved/found so we can call * {@link #startActivity(Intent)} or {@link #startActivityForResult(Intent, int)} with that * intent, {@code false} otherwise, that means that there is no activity currently installed on * actual Android device that can handle that intent. */ @CheckResult @SuppressWarnings("ConstantConditions") protected boolean hasActivityForIntent(@NonNull Intent intent) { final Context context = mContextWrapper.getContext(); return intent.resolveActivity(context.getPackageManager()) != null; } /** * Creates an instance of {@link AndroidRuntimeException} indicating that one of required parameters * for this intent builder has not been specified, but {@link #build()} has been invoked. * <p> * This can be invoked from {@link #build()} to handle such situation. * * @param message Message to be included into exception. */ protected final AndroidRuntimeException cannotBuildIntentException(@NonNull String message) { final String intentName = getClass().getSimpleName(); return new AndroidRuntimeException("Cannot build " + intentName + ". " + message); } /** * Invoked whenever {@link #start()} is called and there is an activity available that can handle * intent created via {@link #build()}. * * @param intent Instance of the Intent created via {@link #build()}. * @return {@code True} to indicate that the intent has been started, {@code false} otherwise. */ @CheckResult protected boolean onStart(@NonNull Intent intent) { startActivity(intent); return true; } /** * Same as {@link IntentContextWrapper#startIntent(Intent)}. * * @param intent The instance of Intent to start. */ protected final void startActivity(@NonNull Intent intent) { mContextWrapper.startIntent(intent); if (mApplyTransitions) { mContextWrapper.overridePendingTransition(mEnterTransition, mExitTransition); } } /** * Same as {@link IntentContextWrapper#startIntentForResult(Intent, int)}. * <p> * Note, that here is also handled {@link android.content.ActivityNotFoundException} exception. * * @param intent The instance of Intent to start. * @param requestCode Request code for the intent. */ protected final void startActivityForResult(@NonNull Intent intent, @IntRange(from = 0, to = Integer.MAX_VALUE) int requestCode) { mContextWrapper.startIntentForResult(intent, requestCode); if (mApplyTransitions) { mContextWrapper.overridePendingTransition(mEnterTransition, mExitTransition); } } /** * Notifies message for situation, when there wasn't found any activity to handle started intent. * <p> * Actually, this shows a toast with the message passed to {@link #activityNotFoundMessage(CharSequence)}. * * @see #activityNotFoundMessage(CharSequence) */ protected void notifyActivityNotFound() { final Context context = mContextWrapper.getContext(); if (context != null) { Toast.makeText(context, mActivityNotFoundMessage, Toast.LENGTH_LONG).show(); } } /** * Inner classes =============================================================================== */ }