Esta charla comprende las lecciones aprendidas convirtiendo la app de Android de Teambox (una app repleta de deuda técnica y con un alto nivel de acoplamiento entre clases), en la versión actual de Redbooth, que intenta cumplir la arquitectura Hexagonal y los principios SOLID. Durante la exposición explicaremos como fuimos desenredando el código paso a paso; como aplicamos por partes los conceptos de la arquitectura hexagonal; como dejamos de lado componentes del framework de Android que dificultaban el mantenimiento de la app; y que errores cometimos, como los solucionamos y como se podrían haber evitado.
Post Quantum Cryptography – The Impact on Identity
From Legacy to Hexagonal (An Unexpected Android Journey)
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. Agenda
1. From Legacy Code
2. Towards Hexagonal Architecture
3. To infinity, and beyond!
28. 1. Slice the big methods into small
meaningful methods
29. 1. Slice the big methods into small
meaningful methods
2. Identify different responsibilities and move
them to other classes
30. 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)
41. listAdapter =
MainFragment
new SimpleCursorAdapter(getActivity(),
android.R.layout.simple_list_item_1,
null,
new String[]{FakeDatabase.COLUMN_NAME},
new int[]{android.R.id.text1},
0);
listView.setAdapter(listAdapter);
42. 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
44. MainView & MainModel
public interface MainView {
public void swaplListData(Cursor cursor);
}
public interface MainModel {
public void setPresenter(MainPresenter presenter);
public void startLoadingData(Context context);
}
45. 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);
}
46. 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);
}
}
47. PresenterFactory
public class PresenterFactory {
public static MainPresenter getMainPresenter(MainView view) {
MainModel model = new MainCursorModel();
return MainPresenter.newInstance(view, model);
}
}
48. 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);
}
}
}
49. 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
67. 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;
}
68. 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?
83. 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