Magren

Magren

Idealist & Garbage maker 🛸
twitter
jike

Design of Android Base Classes

The winter vacation started these days, and since I'm 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#

  • Convenient code writing, reducing duplicate code and redundant logic, optimizing code
  • Optimizing program architecture, reducing coupling, facilitating expansion and modification
  • Cleaner layout, reducing the occupation of layout by repeated logic such as lifecycle logs

Basic Design Ideas#

  • Lifecycle debugging log output
  • Binding views
  • Common OnClick methods
  • Back methods, Toast, and Activity operations
  • Common third-party tools (like ButterKnife)
  • Whether the title bar is displayed, whether it is full screen, initializing 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 mode
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        setContentView(bindLayout());
        // ButterKnife binds the layout
        bind = ButterKnife.bind(this);

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

        // Prepare data
        prepareData();
        // Initialize title bar
        initToolbar();
        // Initialize view
        initView();
        // Initialize data
        initData(savedInstanceState);
        // Initialize event listeners
        initEvent();
    }


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

    /**
     * Implement the setPresenter method of BasePresenter interface
     *
     * @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 initialization)
     */
    protected abstract void prepareData();

    /**
     * Initialize view, 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 the 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 the 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();
        // (Automatically called by SDK in applications with only Activity, no need to write separately)
        // Ensure onPageEnd is called before onPause, because onPause will save information.
        MobclickAgent.onPageEnd(this.getClass().getSimpleName());
        MobclickAgent.onPause(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        // Statistics page (automatically called by SDK in applications with only Activity, 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 view
        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, allowing for clear visibility of the entire page's logic through standardized method naming or comments.

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, for example: load data from the database or network requests, etc.
    }

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

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

    @Override
    protected void initEvent() {
        //TODO: Initialize event listeners, for example: 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.