Clean Your Activity Using the Delegation Pattern

Whenever you add a navigation drawer to your app, you face the challenge of a lot of boilerplate code that pollutes the Activity class. This large amount of boilerplate code distracts your attention from the app’s main logic, and can lead to overproliferation of code.

There are a lot of ways to clean your code, and most do work. But we want to show you one more way that is often wrongly neglected – cleaning your code with the help of the Delegation pattern.

We’ll take creating a side menu as an example, since side menus are used in most big apps and this is a good design pattern for navigation.

clean-your-activity-using-delegation-pattern-side-menu

[Image source: TechnoTalkative]

Creating a template project with a side menu

I decided to create a template project based on the Model–View–Presenter (MVP) pattern to solve the problem of boilerplate code.

First, I created an abstract layer and then moved to the implementation. Let’s start with creating the base classes. Base classes help you follow the MVP pattern.

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

    private Unbinder mUnBinder;

    private ProgressDialog mProgressDialog = null;

    protected @NonNull abstract P getPresenterInstance();

    protected P mPresenter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPresenter = getPresenterInstance();
        mPresenter.attachView(this);
    }

    @Override
    public void setContentView(@LayoutRes int layoutResID) {
        super.setContentView(layoutResID);
        mUnBinder = ButterKnife.bind(this);
    }

    @Override
    protected void onDestroy() {
        mPresenter.detachView();
        mUnBinder.unbind();
        super.onDestroy();
    }

    @Override
    public View getContentView() {
        return getWindow().getDecorView();
    }
}

All Activities in the project inherit from the BaseActivity class. You can create a similar class for Fragments. I used ButterKnife for view binding.

public interface BasePresenter<V extends BaseView> {

    void attachView(V view);

    void detachView();

}
public class BasePresenterImpl<V extends BaseView> implements BasePresenter<V> {

    protected V mView;

    @Override
    public void attachView(V view) {
        mView = view;
    }

    @Override
    public void detachView() {
        mView = null;
    }

    public V getView() {
        return mView;
    }

}

This is a simple implementation of a base presenter interface, which interacts with the view instance.

public interface BaseView {

    void showProgress();

    void hideProgress();

    View getContentView();

}

This is the BaseView interface. All Activities and Fragments implement it.

You can check out all these classes in the template’s base package. This set of classes demonstrates a basic MVP architecture and provides the foundation on which our example is built. I also recommend visiting Android Architecture Blueprints for more details about the MVP architecture.

clean-your-activity-using-delegation-pattern-layers

[Image source: macoscope]

Cleaning the Activity class with the Delegation pattern

You can use the Delegation pattern to reduce the amount of code in the Activity class. With the Delegation pattern, an instance handles a request by delegating it to a second object (a delegate). Keep in mind that you have to deal with Android platform’s peculiarities and adapt the Delegate class to the activity lifecycle to avoid memory leaks.

clean-your-activity-using-delegation-pattern-activity-lifecycle

[Image source: AbhiAndroid]

First, let’s create base classes that handle routine functions.

public abstract class BaseActivityDelegate<
        V extends BaseView,
        P extends BasePresenterImpl<V>> {

    private Unbinder mUnBinder = null;

    protected P mPresenter;

    public void onCreate(P presenter) {
        mPresenter = presenter;
        mUnBinder = ButterKnife.bind(this, mPresenter.getView().getContentView());
    }

    public void onDestroy() {
        mUnBinder.unbind();
    }
}

In the code sample above, you can see the base class for delegation. This base class contains Presenter and View instances so we can deal with different functionality. Converting base classes into universal classes can be a bit challenging. But you can use generics to universalize them and use ButterKnife to access views.

public abstract class BaseDelegationActivity<
        V extends BaseView,
        P extends BasePresenterImpl<V>,
        D extends BaseActivityDelegate<V, P>>
        extends BaseActivity<P> {

    protected D mDelegate;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mDelegate = instantiateDelegateInstance();
        mDelegate.onCreate(mPresenter);
    }

    protected abstract D instantiateDelegateInstance();

    @Override
    protected void onDestroy() {
        mDelegate.onDestroy();
        super.onDestroy();
    }
}

The BaseDelegationActivity abstract class extends BaseActivity and handles challenges with the lifecycle. Inheritors of BaseActivity will delegate tasks to heirs of BaseActivityDelegate. Here’s the result:

public class NavigationDrawerDelegate extends BaseActivityDelegate<
        NavigationDrawerContract.NavigationDrawerView,
        NavigationDrawerPresenter> implements NavigationView.OnNavigationItemSelectedListener {

    @BindView(R.id.drawer_layout)
    protected DrawerLayout mDrawerLayout;
    @BindView(R.id.toolbar)
    protected Toolbar mToolBar;
    @BindView(R.id.nav_view)
    protected NavigationView mNavigationView;

    @Override
    public void onCreate(NavigationDrawerPresenter presenter) {
        super.onCreate(presenter);
        configureDrawer();
    }

    private void configureDrawer() {
        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
                mPresenter.getView().getActivity(),
                mDrawerLayout,
                mToolBar,
                R.string.navigation_drawer_open,
                R.string.navigation_drawer_close);
        mPresenter.getView().getActivity().getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        mDrawerLayout.addDrawerListener(toggle);
        toggle.syncState();
        mNavigationView.setNavigationItemSelectedListener(this);
    }

    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()) {
            case R.id.nav_camera:
                mPresenter.getView().openCamera();
                break;
            case R.id.nav_gallery:

                break;
            case R.id.nav_slideshow:

                break;
            case R.id.nav_manage:

                break;
            case R.id.nav_share:

                break;
            case R.id.nav_send:
                mPresenter.doSomething();
                break;
        }
        closeDrawer();
        return true;
    }

    public boolean closeDrawer() {
        if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
            mDrawerLayout.closeDrawer(GravityCompat.START);
            return true;
        } else {
            return false;
        }
    }
}

All the logic of the navigation drawer is contained in NavigationDrawerDelegate.

public class NavigationDrawerActivity extends BaseDelegationActivity<
        NavigationDrawerContract.NavigationDrawerView,
        NavigationDrawerPresenter,
        NavigationDrawerDelegate>
        implements NavigationDrawerContract.NavigationDrawerView {

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

    public static Intent newIntent(Context context) {
        return new Intent(context, NavigationDrawerActivity.class);
    }

    @Override
    protected NavigationDrawerDelegate instantiateDelegateInstance() {
        return new NavigationDrawerDelegate();
    }

    @NonNull
    @Override
    protected NavigationDrawerPresenter getPresenterInstance() {
        return new NavigationDrawerPresenter();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        setContentView(R.layout.activity_navigation_drawer);
        setSupportActionBar(mToolbar);
        super.onCreate(savedInstanceState);
    }

    @Override
    public NavigationDrawerActivity getActivity() {
        return this;
    }

    @OnClick(R.id.fab)
    public void onFabClick(View view) {
        Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                .setAction("Action", null).show();
    }

    @Override
    public void onBackPressed() {
        if (!mDelegate.closeDrawer()) {
            super.onBackPressed();
        }
    }

    @Override
    public void onSomethingDone() {
        Snackbar.make(getContentView(), "Done", Snackbar.LENGTH_LONG)
                .setAction("Action", null).show();
    }

    @Override
    public void openCamera() {
        Snackbar.make(getContentView(), "open camera", Snackbar.LENGTH_LONG)
                .setAction("Action", null).show();
    }
}

So there you have it –  your Activity class is now much cleaner.

The Activity class is an example of the God object anti-pattern, which is why we face the problem of over-proliferation. With the Delegation pattern, you can move part of your code into a separate class to reduce the amount of boilerplate code. If you want to find out more, you can read about delegation in Kotlin in one of our previous articles.

Read also: How to Work with Delegation in Kotlin

4.5/ 5.0
Article rating
15
Reviews
Remember those Facebook reactions? Well, we aren't Facebook but we love reactions too. They can give us valuable insights on how to improve what we're doing. Whould you tell us how you feel about this article?