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.
}
}