Upgrade to Pro — share decks privately, control downloads, hide ads and more …

From Legacy to Hexagonal (Codemotion Madrid 2014)

Rubén Serrano
November 24, 2014
350

From Legacy to Hexagonal (Codemotion Madrid 2014)

Rubén Serrano

November 24, 2014
Tweet

Transcript

  1. From Legacy to Hexagonal (An Unexpected Android Journey) Rubén Serrano

    @Akelael Lead Android Developer @RedboothHQ José Manuel Pereira @JMPergar Android Software Engineer @RedboothHQ
  2. A different Android dev One dev from a contractor +

    one senior iOS dev + one junior iOS dev
  3. A different Android dev One dev from a contractor +

    one senior iOS dev + one junior iOS dev + one confused Android team
  4. 1. We have huge technical debt 2. We can’t stop

    developing new features 3. We can’t remove debt at this point and we shouldn’t add any more
  5. 1. Slice the big methods into small meaningful methods 2.

    Identify different responsibilities and move them to other classes
  6. 1. Slice the big methods into small meaningful methods 2.

    Identify different responsibilities and move them to other classes 3. Use less coupled framework components (or no components at all)
  7. 
 getLoaderManager().initLoader(0, null, new LoaderManager.LoaderCallbacks<Cursor>() {
 @Override
 public Loader<Cursor> onCreateLoader(int

    id, Bundle args) {
 return new CursorLoader(getActivity(), MainContentProvider.URI, null, null, null, null);
 }
 
 @Override
 public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
 listAdapter.swapCursor(data);
 }
 
 @Override
 public void onLoaderReset(Loader<Cursor> loader) {
 listAdapter.swapCursor(null);
 }
 }); MainFragment
  8. MainView & MainModel public interface MainView {
 public void swaplListData(Cursor

    cursor);
 } public interface MainModel {
 public void setPresenter(MainPresenter presenter);
 public void startLoadingData(Context context);
 }
  9. MainFragment public class MainFragment extends Fragment implements MainView {
 //...


    
 @Override
 public void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 presenter = PresenterFactory.getMainPresenter(this);
 } @Override
 public void onActivityCreated(Bundle savedInstanceState) {
 //...
 presenter.notifyOnCreate(getActivity());
 } @Override
 public void swaplListData(Cursor cursor) {
 listAdapter.swapCursor(cursor);
 }
  10. MainPresenter public class MainPresenter {
 private MainView mainView;
 private MainModel

    mainModel;
 
 //...
 
 public void notifyOnCreate(Context context) {
 mainModel.startLoadingData(context);
 }
 
 public void notifiyLoadedDataAvailable(Cursor cursor) {
 mainView.swaplListData(cursor);
 }
 }
  11. PresenterFactory public class PresenterFactory {
 public static MainPresenter getMainPresenter(MainView view)

    {
 MainModel model = new MainCursorModel();
 return MainPresenter.newInstance(view, model);
 }
 }
  12. MainCursorModel public class MainCursorModel implements MainModel {
 //... 
 @Override


    public void startLoadingData(Context context) {
 new LoadDataAsyncTask().execute(context);
 }
 
 private class LoadDataAsyncTask extends AsyncTask<Context, Void, Cursor > {
 //... 
 @Override
 protected void onPostExecute(Cursor result) {
 super.onPostExecute(result);
 presenter.notifiyLoadedDataAvailable(result);
 }
 }
 }
  13. Pros & Cons View decoupled from model Cleaner code and

    smaller fragment/activities View and model not really decoupled (cursor) All the components use the framework
  14. Module Core Module App Module App Module App Module App

    Core Core Core Core App App App App
  15. MainFragment public class MainFragment extends Fragment {
 //...
 private MainFragmentBoundary

    viewBoundary;
 
 @Override
 public void onCreate(Bundle savedInstanceState) {
 //...
 viewBoundary = MainFragmentBoundary.newInstance(this);
 }
 
 @Override
 public void onActivityCreated(Bundle savedInstanceState) {
 //...
 viewBoundary.notifyOnCreate();
 }
  16. MainFragment public void setListAdapter() {
 listAdapter = new ArrayAdapter<String>(getActivity(),
 android.R.layout.simple_list_item_1,


    android.R.id.text1,
 new ArrayList<String>(0));
 listView.setAdapter(listAdapter);
 }
 
 public void swapList(List<String> names) {
 listAdapter.clear();
 listAdapter.addAll(names);
 }
  17. MainFragmentBoundary public class MainFragmentBoundary implements MainViewPort {
 private MainFragment mainFragment;


    private MainLogic logic;
 
 //...
 public void notifyOnCreate() {
 logic.notifyOnCreate();
 } 
 @Override
 public void swaplListData(List<String> names) {
 mainFragment.swapList(names);
 }
  18. MainModelBoundary public class MainModelBoundary implements MainModelPort {
 private MainLogic logic;


    private MainRepository repository;
 //...
 
 @Override
 public void startLoadingData() {
 repository.startLoadingData(new MainRepository.OnDataLoadedListener() {
 @Override
 public void onDataLodaded(Cursor cursor) {
 notifyDataLoaded(cursor);
 }
 });
 }
 
 private void notifyDataLoaded(Cursor cursor) {
 List<String> names = mapCursorToList(cursor);
 logic.notifiyLoadedDataAvailable(names);
 }
  19. MainModelBoundary private List<String> mapCursorToList(Cursor cursor) {
 List<String> names = new

    ArrayList<String>();
 int nameColumnIndex = cursor.getColumnIndex(FakeDatabase.COLUMN_NAME);
 while (cursor.moveToNext()) {
 String name = cursor.getString(nameColumnIndex);
 names.add(name);
 }
 return names;
 }
  20. Pros & Cons Logic is not going to be affected

    by framework changes Logic is pure Java: easier to test Less changes when replacing a plugin Easier for 2 devs to work on the same feature More complex architecture Need to map each POJO for each layer What happens when the plugins need to cooperate?
  21. We don’t like: callback’s hell + AsyncTasks We don’t mind

    (but could be a problem): only commands in separate threads
  22. We don’t like: callback’s hell + AsyncTasks We don’t mind

    (but could be a problem): only commands in separate threads We would love: RxJava
  23. Model Plugin Layout + Activity/ Fragment Presenter Use Case Model

    Plugin Use Case Repository Model Plugin Use Case
  24. When? • If you expect 2+ devs working on the

    same feature • Unless you are sure the app is going to die in a near future • You know for sure you will change your plugins