Magren

Magren

Idealist & Garbage maker 🛸
twitter
jike

Design of Android Base Classes

The winter vacation started these days, and since I was just staying at home, I decided to learn from the projects written by the Android experts at Xingkong, to see the structure of others' code and what methods can reduce code coupling. Then I came across BaseActivity in my senior's project, and I want to record it here.

Why Design a Base Class#

  • Facilitate code writing, reduce duplicate code and redundant logic, optimize code
  • Optimize program architecture, reduce coupling, facilitate expansion and modification
  • Cleaner layout, reducing the occupation of layout by repeated logic such as lifecycle logs

Basic Design Ideas#

  • Lifecycle debugging log output
  • Bind views
  • Common OnClick methods
  • Back methods, Toast, and Activity operations
  • Common third-party tools (like ButterKnife)
  • Whether to display the title bar, whether to be full screen, initialize data, etc.

Specific Base Class Encapsulation#

BaseActivity#

public abstract class BaseActivity<P extends BasePresenter> extends AppCompatActivity
        implements BaseView<P> {

    @BindView(R.id.toolbar)
    Toolbar mToolbar;

    Unbinder bind;

    /**
     * Progress dialog
     */
    protected ProgressDialog mProgressDialog;
    /**
     * Generic Presenter
     */
    protected P mPresenter;

    @Override
    protected final void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Set to portrait
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        setContentView(bindLayout());
        // ButterKnife bind layout
        bind = ButterKnife.bind(this);

        mPresenter = createPresenter();
        if (mPresenter != null) {
            // Call Presenter initialization method
            mPresenter.onStart();
        }

        // Prepare data
        prepareData();
        // Initialize toolbar
        initToolbar();
        // Initialize views
        initView();
        // Initialize data
        initData(savedInstanceState);
        // Initialize event listeners
        initEvent();
    }


    /**
     * Create Presenter
     *
     * @return Generic Presenter
     */
    protected abstract P createPresenter();

    /**
     * Implement BasePresenter interface's setPresenter method
     *
     * @param presenter Presenter created by createPresenter()
     */
    @Override
    public void setPresenter(P presenter) {
        mPresenter = presenter;
    }

    /**
     * Initialize Toolbar
     */
    private void initToolbar() {
        setSupportActionBar(mToolbar);
    }

    /**
     * Set Toolbar title
     *
     * @param title Title
     */
    protected void setToolbarTitle(String title) {
        if (getSupportActionBar() != null) {
            getSupportActionBar().setTitle(title);
        }
    }

    /**
     * Set Toolbar to show back button and title
     *
     * @param title Title
     */
    protected void setToolbarBackEnable(String title) {
        if (getSupportActionBar() != null) {
            getSupportActionBar().setDisplayHomeAsUpEnabled(true);
            getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_arrow_white_24dp);
            setToolbarTitle(title);
        }

    }

    /**
     * Bind layout
     *
     * @return Resource ID of the layout file
     */
    protected abstract int bindLayout();

    /**
     * Prepare data (get data passed from the previous screen via Intent or other data that needs to be initialized)
     */
    protected abstract void prepareData();

    /**
     * Initialize views, findViewById, etc.
     */
    protected abstract void initView();

    /**
     * Initialize data, start fetching data from local or server
     *
     * @param savedInstanceState Data saved when the interface is not normally destroyed
     */
    protected abstract void initData(Bundle savedInstanceState);

    /**
     * Initialize event listeners, setOnClickListener, etc.
     */
    protected abstract void initEvent();

    /**
     * Implement BaseView's showToast(CharSequence msg)
     *
     * @param msg Message to display in Toast
     */
    @Override
    public void showToast(CharSequence msg) {
        ToastUtils.shortToast(this, msg);
    }

    /**
     * Implement BaseView's showToast(int msgId)
     *
     * @param msgId String resource ID to display in Toast
     */
    @Override
    public void showToast(int msgId) {
        ToastUtils.shortToast(this, msgId);
    }

    /**
     * Implement BaseView's showLoadingDialog(CharSequence msg)
     * Show loading dialog
     *
     * @param msg Content of the dialog prompt
     */
    @Override
    public void showLoadingDialog(CharSequence msg) {
        if (mProgressDialog == null) {
            mProgressDialog = new ProgressDialog(this);
            mProgressDialog.setTitle(R.string.title_dialog_tips);
            mProgressDialog.setMessage(msg);
        } else {
            mProgressDialog.setTitle(R.string.title_dialog_tips);
        }
        mProgressDialog.show();
    }

    /**
     * Implement BaseView's hideLoadingDialog()
     * Hide loading dialog
     */
    @Override
    public void hideLoadingDialog() {
        if (mProgressDialog != null && mProgressDialog.isShowing()) {
            mProgressDialog.dismiss();
        }
    }

    /**
     * Clean up resources when Activity is destroyed
     */
    @Override
    protected void onDestroy() {
        // ButterKnife unbind
        bind.unbind();
        // Destroy Presenter
        if (mPresenter != null) {
            mPresenter.onDestroy();
        }
        super.onDestroy();
    }

    /**
     * Hide keyboard
     */
    public void hideKeyboard() {
        View view = getCurrentFocus();
        if (view != null) {
            ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)).
                    hideSoftInputFromWindow(view.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        // (Only in applications with Activity, SDK automatically calls, no need to write separately)
        // Ensure onPageEnd is called before onPause, because information will be saved in onPause.
        MobclickAgent.onPageEnd(this.getClass().getSimpleName());
        MobclickAgent.onPause(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        // Statistics page (only in applications with Activity, SDK automatically calls, no need to write separately.)
        MobclickAgent.onPageStart(this.getClass().getSimpleName());
        // Statistics duration
        MobclickAgent.onResume(this);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == android.R.id.home) {
            finish();
        }
        return super.onOptionsItemSelected(item);
    }
}

BaseFragment#

public abstract class BaseFragment<P extends BasePresenter> extends Fragment
        implements BaseView<P> {

    @BindView(R.id.toolbar)
    Toolbar mToolbar;

    private Unbinder mUnbinder;
    protected P mPresenter;

    protected ProgressDialog mProgressDialog;

    private ActionBar mActionbar;

    @Nullable
    @Override
    public final View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        mPresenter = createPresenter();
        if (mPresenter != null) {
            mPresenter.onStart();
        }
        View root = inflater.inflate(bindLayout(), container, false);
        mUnbinder = ButterKnife.bind(this, root);
        prepareData(savedInstanceState);
        initToolbar();
        // Initialize views
        initView(root);
        initData(savedInstanceState);
        initEvent();
        return root;
    }


    protected abstract P createPresenter();


    @Override
    public void setPresenter(P presenter) {
        mPresenter = presenter;
    }

    /**
     * Prepare data
     *
     * @param savedInstanceState
     */
    protected abstract void prepareData(Bundle savedInstanceState);

    /**
     * Bind fragment's layout file
     *
     * @return
     */
    protected abstract int bindLayout();

    /**
     * Initialize data
     *
     * @param savedInstanceState
     */
    protected abstract void initData(Bundle savedInstanceState);

    /**
     * Initialize interface
     *
     * @param rootView
     */
    protected abstract void initView(View rootView);

    /**
     * Initialize event listeners
     */
    protected abstract void initEvent();


    /**
     * Initialize Toolbar
     */
    private void initToolbar() {
        ((AppCompatActivity) getActivity()).setSupportActionBar(mToolbar);
        mActionbar = ((AppCompatActivity) getActivity()).getSupportActionBar();
    }

    /**
     * Set Toolbar title
     *
     * @param title Title
     */
    protected void setToolbarTitle(String title) {
        if (mActionbar != null) {
            mActionbar.setTitle(title);
        }
    }

    /**
     * Set Toolbar to show back button and title
     *
     * @param title Title
     */
    protected void setToolbarBackEnable(String title) {
        if (mActionbar != null) {
            mActionbar.setDisplayHomeAsUpEnabled(true);
            mActionbar.setHomeAsUpIndicator(R.drawable.ic_arrow_white_24dp);
        }
    }

    @Override
    public void showToast(CharSequence msg) {
        ToastUtils.shortToast(APP.getAppContext(), msg);
    }

    @Override
    public void showToast(int msgId) {
        ToastUtils.shortToast(APP.getAppContext(), msgId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Override
    public void showLoadingDialog(CharSequence msg) {
        if (mProgressDialog == null) {
            mProgressDialog = new ProgressDialog(getContext());
            mProgressDialog.setMessage(msg);
        } else {
            mProgressDialog.setTitle(R.string.title_dialog_tips);
        }
        mProgressDialog.show();
    }

    @Override
    public void hideLoadingDialog() {
        if (mProgressDialog != null && mProgressDialog.isShowing()) {
            mProgressDialog.dismiss();
        }
    }

    @Override
    public void onDetach() {
        mUnbinder.unbind();
        super.onDetach();
    }

    @Override
    public void onDestroyView() {
        if (mPresenter != null) {
            mPresenter.onDestroy();
        }
        super.onDestroyView();
    }

    @Override
    public void onResume() {
        super.onResume();
        MobclickAgent.onPageStart(this.getClass().getSimpleName());
    }

    @Override
    public void onPause() {
        super.onPause();
        MobclickAgent.onPageEnd(this.getClass().getSimpleName());
    }

}

BasePresenter#

public interface BasePresenter {

    void onStart();

    void onDestroy();

}

BaseView#

public interface BaseView<P> {

    void setPresenter(P presenter);

    void showToast(CharSequence msg);

    void showToast(int msgId);

    void showLoadingDialog(CharSequence msg);

    void hideLoadingDialog();

}

Impl Class#

public abstract class BasePresenterImpl implements BasePresenter {
    protected CompositeDisposable mSubscriptions;

    @Override
    public void onStart() {
        if (mSubscriptions == null) {
            mSubscriptions = new CompositeDisposable();
        }
    }

    @Override
    public void onDestroy() {
        if (mSubscriptions != null) {
            mSubscriptions.dispose();
            mSubscriptions.clear();
        }
    }
}

APP Class#

Used to get the global context

public class APP extends Application {

    private static Context appContext;
    private static long exitTime = 0;

    /**
     * Get Application's Context
     *
     * @return Global Context
     */
    public static Context getAppContext() {
        return appContext;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        appContext = getApplicationContext();
    }

    /**
     * Exit APP
     */
    public static void exitApp() {
        if (System.currentTimeMillis() - exitTime > 2000) {
            ToastUtils.shortToast(getAppContext(), appContext.getString(R.string.text_press_again));
            exitTime = System.currentTimeMillis();
        } else {
            android.os.Process.killProcess(android.os.Process.myPid());
        }
    }
}

Specific Usage#

Contract Class#

The contract class is used to define the interfaces of the view and presenter for the same interface. By standardizing method naming or comments, the entire page logic can be clearly seen.

public interface myContract {

    interface View extends BaseView<Presenter> {
    }

    interface Presenter extends BasePresenter {

    }
}

Presenter#

public class SamplePresenter extends BasePresenterImpl implements myContract.Presenter {

    private final myContract.View mView;
    public SamplePresenter(myContract.View view) {
        mView = view;
        this.mView.setPresenter(this);
    }
}

Activity#

public class SampleActivity extends BaseActivity<myContract.Presenter> implements myContract.View {

    @BindView(R.id.tv_sample_text)
    TextView mTvSample;

    @Override
    protected myContract.Presenter createPresenter() {
        return new SamplePresenter(this);
    }

    @Override
    protected int bindLayout() {
        //TODO: Add view, remember to add to androidmanifest
        return R.layout.activity_sample;
    }

    @Override
    protected void prepareData() {
        //TODO: Prepare data, e.g., load data from the database or network request data, etc.
    }

    @Override
    protected void initView() {
        //TODO: Initialize views, e.g., prepare recycleview, add adapter, etc.
        mTvSample.setText("This is a sample");
    }

    @Override
    protected void initData(Bundle savedInstanceState) {
        //TODO: Initialize data, e.g., add data to the view
    }

    @Override
    protected void initEvent() {
        //TODO: Initialize event listeners, e.g., add listeners, pull down to refresh, load more, etc.
    }
}
Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.