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