Save 37% off PRO during our Black Friday Sale! »

How to Survive a Legacy Code Apocalypse on Android

7ace70cd355db1983dea895fbe01a4ef?s=47 rallat
November 22, 2014

How to Survive a Legacy Code Apocalypse on Android

As a platform matures, the app's codebase is getting bigger. In these big codebases, there is legacy code all over the place. Working on that environment is highly unpleasant, without applying the right strategies to refactor this smelly code.

Attend this class to first learn the design principles, patterns and how to apply them on Android and the payoff. Second, you will learn how you could apply these to legacy code. Testing will play a huge role in this.

The class will be really pragmatic, and the idea is to show a pattern and how to apply it to a real piece of code. You will see principles and patterns and how to apply them on Android. Having a strong test suite is key for refactoring and improving your code and will give you the ability to make changes faster without breaking features.

http://www.andevcon.com/classes#HowtoSurviveaLegacyCodeApocalypseonAndroid

7ace70cd355db1983dea895fbe01a4ef?s=128

rallat

November 22, 2014
Tweet

Transcript

  1. How to Survive a Legacy Code Apocalypse on Android November

    21, 2014 #legacyAndroid
  2. Israel Ferrer Camacho Senior Software Engineer @rallat

  3. None
  4. None
  5. None
  6. Building First Class SDKs November 21, 2014 Ty Smith Gran

    Peninsula EF
  7. How to Survive a Legacy Code Apocalypse on Android November

    21, 2014 #legacyAndroid
  8. What is it?

  9. “Legacy code is simply code without tests." - Feathers, Michael

  10. None
  11. Why should I care?

  12. BadView.java Lines 6301 Methods 180

  13. Test

  14. Cyclomatic Complexity Number setContent 181 onMeasure 142 getReasonString 92

  15. Legacy code make teams slow

  16. How to detect it?

  17. Code Smells Are symptoms that something in the code is

    wrong.
  18. Code Smells • Duplicated code • Long method • Large

    class • Long parameter list • Comments/Deodorant • More about Code smells in the book: “Improving the Design of Existing Code”
  19. Automate all the things!

  20. Gerrit git push gerrit HEAD:refs/for/master test jobs static analysis Reviewer

    +1 +2
  21. Sonar

  22. Sonar

  23. How to clean it?

  24. You need a SOLID code

  25. SOLID • Single Responsibility • Open-Close • Liskov substitution •

    Interface segregation • Dependency inversion Read OOD principles in this essay by Robert C. Martin
  26. TL;DR: Separation of concerns and Don’t repeat yourself (DRY) are

    the most impactful principles
  27. How to work with it?

  28. Keep the focus in one task.

  29. The hat metaphor: You can only wear one hat at

    a time
  30. The hat metaphor: You can only wear one hat at

    a time
  31. The hat metaphor: You can only wear one hat at

    a time
  32. • Add test to the method you need to modify

    the behavior. • Refactor legacy code in order to make code testable. • Add test for this new behavior. • Implement the new behavior. • Improve the design of this new behavior. • Verify that all tests passed.
  33. Show me the code!

  34. None
  35. None
  36. package alexandria.israelferrer.com.libraryofalexandria;
 
 import android.app.Activity;
 import android.content.Context;
 import android.content.SharedPreferences;
 import

    android.os.Bundle;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewStub;
 import android.widget.BaseAdapter;
 import android.widget.CheckBox;
 import android.widget.CompoundButton;
 import android.widget.ImageView;
 import android.widget.ListView;
 import android.widget.RatingBar;
 import android.widget.TextView;
 
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
 import com.squareup.picasso.Picasso;
 
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.lang.reflect.Type;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 
 
 public class MainActivity extends Activity {
 private static final String PACKAGE = "com.israelferrer.alexandria";
 private static final String KEY_FAVS = PACKAGE + ".FAVS";
 private List<ArtWork> artWorkList;
 private ArtWorkAdapter adapter;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 
 setContentView(R.layout.activity_main);
 ListView listView = (ListView) findViewById(R.id.listView);
 InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {
 }.getType();
 artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 
 adapter = new ArtWorkAdapter();
 listView.setAdapter(adapter);
 
 }
 
 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
 // Inflate the menu; this adds items to the action bar if it is present.
 getMenuInflater().inflate(R.menu.menu_main, menu);
 return true;
 }
 
 public boolean onOptionsItemSelected(MenuItem item) {
 // Handle action bar item clicks here. The action bar will
 // automatically handle clicks on the Home/Up button, so long
 // as you specify a parent activity in AndroidManifest.xml.
 int id = item.getItemId();
 
 //noinspection SimplifiableIfStatement
 if (id == R.id.filter) {
 adapter.orderMode();
 return true;
 }
 
 return super.onOptionsItemSelected(item);
 }
 
 private class ArtWorkAdapter extends BaseAdapter {
 
 private boolean isOrder;
 private final List<ArtWork> orderedList;
 
 public ArtWorkAdapter() {
 super();
 orderedList = new LinkedList<ArtWork>();
 }
 
 @Override
 public int getCount() {
 return artWorkList.size();
 }
 
 @Override
 public Object getItem(int position) {
 return artWorkList.get(position);
 }
 
 @Override
 public long getItemId(int position) {
 return Long.valueOf(artWorkList.get(position).getId());
 }
 
 public void orderMode() {
 isOrder = !isOrder;
 if (isOrder) {
 orderedList.clear();
 orderedList.addAll(artWorkList);
 Collections.sort(orderedList);
 notifyDataSetChanged();
 } else {
 notifyDataSetChanged();
 }
 }
 
 @Override
 public View getView(int position, View convertView, ViewGroup parent) {
 final ArtWork artWork;
 if (isOrder) {
 artWork = orderedList.get(position);
 } else {
 artWork = artWorkList.get(position);
 }
 View row;
 
 switch (artWork.getType()) {
 case ArtWork.QUOTE:
 row = getLayoutInflater().inflate(R.layout.text_row, null);
 TextView quote = (TextView) row.findViewById(R.id.quote);
 TextView author = (TextView) row.findViewById(R.id.author);
 
 quote.setText("\"" + artWork.getText() + "\"");
 author.setText(artWork.getAuthor());
 break;
 case ArtWork.PAINTING:
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 final HashSet<String> favs = (HashSet<String>) preferences
 .getStringSet(KEY_FAVS,
 new HashSet<String>());
 row = getLayoutInflater().inflate(R.layout.painting_row, null);
 ImageView image = (ImageView) row.findViewById(R.id.painting);
 TextView painter = (TextView) row.findViewById(R.id.author);
 painter.setText(artWork.getTitle() + " by " + artWork.getAuthor());
 Picasso.with(MainActivity.this).load(artWork.getContentUrl()).fit()
 .into(image);
 RatingBar rating = (RatingBar) row.findViewById(R.id.rate);
 rating.setRating(artWork.getRating());
 rating.setOnRatingBarChangeListener(new RatingBar.OnRatingBarChangeListener() {
 @Override
 public void onRatingChanged(RatingBar ratingBar, float rating,
 boolean fromUser) {
 preferences.edit().putFloat(PACKAGE + artWork.getId(), rating).apply();
 artWork.setRating(rating);
 }
 });
 CheckBox fav = (CheckBox) row.findViewById(R.id.fav);
 fav.setChecked(favs.contains(artWork.getId()));
 fav.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
 
 @Override
 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
 final HashSet<String> favs = new HashSet<String>((HashSet<String>)
 preferences
 .getStringSet(KEY_FAVS,
 new HashSet<String>()));
 if (isChecked) {
 favs.add(artWork.getId());
 } else {
 favs.remove(artWork.getId());
 }
 preferences.edit().putStringSet(KEY_FAVS,
 favs).apply();
 }
 });
 break;
 case ArtWork.MOVIE:
 case ArtWork.OPERA:
 row = new ViewStub(MainActivity.this);
 break;
 
 default:
 row = getLayoutInflater().inflate(R.layout.text_row, null);
 }
 return row;
 }
 
 }
 }

  37. import java.util.List;
 
 
 public class MainActivity extends Activity {


    private static final String PACKAGE = "com.israelferrer.alexandria";
 private static final String KEY_FAVS = PACKAGE + ".FAVS";
 private List<ArtWork> artWorkList;
 private ArtWorkAdapter adapter;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 
 setContentView(R.layout.activity_main);
 ListView listView = (ListView) findViewById(R.id.listView);
 InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {
 }.getType();
 artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 
 adapter = new ArtWorkAdapter();
 listView.setAdapter(adapter);
 

  38. adapter = new ArtWorkAdapter();
 listView.setAdapter(adapter);
 
 }
 
 @Override
 public

    boolean onCreateOptionsMenu(Menu menu) {
 // Inflate the menu; this adds items to the action bar if it is present.
 getMenuInflater().inflate(R.menu.menu_main, menu);
 return true;
 }
 
 public boolean onOptionsItemSelected(MenuItem item) {
 // Handle action bar item clicks here. The action bar will
 // automatically handle clicks on the Home/Up button, so long
 // as you specify a parent activity in AndroidManifest.xml.
 int id = item.getItemId();
 
 //noinspection SimplifiableIfStatement
 if (id == R.id.filter) {
 adapter.orderMode();
 return true;
 }
 
 return super.onOptionsItemSelected(item);
 }
 
 private class ArtWorkAdapter extends BaseAdapter {
 

  39. return true;
 }
 
 return super.onOptionsItemSelected(item);
 }
 
 private class

    ArtWorkAdapter extends BaseAdapter {
 
 private boolean isOrder;
 private final List<ArtWork> orderedList;
 
 public ArtWorkAdapter() {
 super();
 orderedList = new LinkedList<ArtWork>();
 }
 
 @Override
 public int getCount() {
 return artWorkList.size();
 }
 
 @Override
 public Object getItem(int position) {
 return artWorkList.get(position);
 }
 
 @Override
 public long getItemId(int position) {

  40. }
 
 @Override
 public long getItemId(int position) {
 return Long.valueOf(artWorkList.get(position).getId());


    }
 
 public void orderMode() {
 isOrder = !isOrder;
 if (isOrder) {
 orderedList.clear();
 orderedList.addAll(artWorkList);
 Collections.sort(orderedList);
 notifyDataSetChanged();
 } else {
 notifyDataSetChanged();
 }
 }
 
 @Override
 public View getView(int position, View convertView, ViewGroup parent) {
 final ArtWork artWork;
 if (isOrder) {
 artWork = orderedList.get(position);
 } else {
 artWork = artWorkList.get(position);
 }
 View row;
 

  41. notifyDataSetChanged();
 } else {
 notifyDataSetChanged();
 }
 }
 
 @Override
 public

    View getView(int position, View convertView, ViewGroup parent) {
 final ArtWork artWork;
 if (isOrder) {
 artWork = orderedList.get(position);
 } else {
 artWork = artWorkList.get(position);
 }
 View row;
 
 switch (artWork.getType()) {
 case ArtWork.QUOTE:
 row = getLayoutInflater().inflate(R.layout.text_row, null);
 TextView quote = (TextView) row.findViewById(R.id.quote);
 TextView author = (TextView) row.findViewById(R.id.author);
 
 quote.setText("\"" + artWork.getText() + "\"");
 author.setText(artWork.getAuthor());
 break;
 case ArtWork.PAINTING:
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 final HashSet<String> favs = (HashSet<String>) preferences
 .getStringSet(KEY_FAVS,
 new HashSet<String>());
 row = getLayoutInflater().inflate(R.layout.painting_row, null);
 ImageView image = (ImageView) row.findViewById(R.id.painting);
 TextView painter = (TextView) row.findViewById(R.id.author);
 painter.setText(artWork.getTitle() + " by " + artWork.getAuthor());
 Picasso.with(MainActivity.this).load(artWork.getContentUrl()).fit()
 .into(image);
 RatingBar rating = (RatingBar) row.findViewById(R.id.rate);
 rating.setRating(artWork.getRating());
 rating.setOnRatingBarChangeListener(new RatingBar.OnRatingBarChangeListener() {
 @Override
 public void onRatingChanged(RatingBar ratingBar, float rating,
 boolean fromUser) {
 preferences.edit().putFloat(PACKAGE + artWork.getId(), rating).apply();
 artWork.setRating(rating);
 }
 });
 CheckBox fav = (CheckBox) row.findViewById(R.id.fav);
 fav.setChecked(favs.contains(artWork.getId()));
 fav.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
 

  42. final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 final HashSet<String> favs

    = (HashSet<String>) preferences
 .getStringSet(KEY_FAVS,
 new HashSet<String>());
 row = getLayoutInflater().inflate(R.layout.painting_row, null);
 ImageView image = (ImageView) row.findViewById(R.id.painting);
 TextView painter = (TextView) row.findViewById(R.id.author);
 painter.setText(artWork.getTitle() + " by " + artWork.getAuthor());
 Picasso.with(MainActivity.this).load(artWork.getContentUrl()).fit()
 .into(image);
 RatingBar rating = (RatingBar) row.findViewById(R.id.rate);
 rating.setRating(artWork.getRating());
 rating.setOnRatingBarChangeListener(new RatingBar.OnRatingBarChangeListener() {
 @Override
 public void onRatingChanged(RatingBar ratingBar, float rating,
 boolean fromUser) {
 preferences.edit().putFloat(PACKAGE + artWork.getId(), rating).apply();
 artWork.setRating(rating);
 }
 });
 CheckBox fav = (CheckBox) row.findViewById(R.id.fav);
 fav.setChecked(favs.contains(artWork.getId()));
 fav.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
 
 @Override
 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
 final HashSet<String> favs = new HashSet<String>((HashSet<String>)
 preferences
 .getStringSet(KEY_FAVS,
 new HashSet<String>()));
 if (isChecked) {
 favs.add(artWork.getId());
 } else {
 favs.remove(artWork.getId());
 }
 preferences.edit().putStringSet(KEY_FAVS,
 favs).apply();
 }
 });
 break;
 case ArtWork.MOVIE:
 case ArtWork.OPERA:
 row = new ViewStub(MainActivity.this);
 break;
 
 default:
 row = getLayoutInflater().inflate(R.layout.text_row, null);
 }
 return row;
 }

  43. Do you smell that? Legacy code

  44. Add test to Activity

  45. @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 
 setContentView(R.layout.activity_main);
 ListView

    listView = (ListView) findViewById(R.id.listView);
 InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {}.getType();
 artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 
 adapter = new ArtWorkAdapter();
 listView.setAdapter(adapter);
 
 }
  46. @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 
 setContentView(R.layout.activity_main);
 ListView

    listView = (ListView) findViewById(R.id.listView);
 InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {}.getType();
 artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 
 adapter = new ArtWorkAdapter();
 listView.setAdapter(adapter);
 
 }
  47. @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 
 setContentView(R.layout.activity_main);
 ListView

    listView = (ListView) findViewById(R.id.listView);
 InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {}.getType();
 artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 
 adapter = new ArtWorkAdapter();
 listView.setAdapter(adapter);
 
 } Loading data
  48. @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 
 setContentView(R.layout.activity_main);
 ListView

    listView = (ListView) findViewById(R.id.listView);
 InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {}.getType();
 artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 
 adapter = new ArtWorkAdapter();
 listView.setAdapter(adapter);
 
 } Loading data
  49. @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 
 setContentView(R.layout.activity_main);
 ListView

    listView = (ListView) findViewById(R.id.listView);
 InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {}.getType();
 artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 
 adapter = new ArtWorkAdapter();
 listView.setAdapter(adapter);
 
 } Loading data Parsing data
  50. @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 
 setContentView(R.layout.activity_main);
 ListView

    listView = (ListView) findViewById(R.id.listView);
 InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {}.getType();
 artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 
 adapter = new ArtWorkAdapter();
 listView.setAdapter(adapter);
 
 } Loading data Parsing data
  51. @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 
 setContentView(R.layout.activity_main);
 ListView

    listView = (ListView) findViewById(R.id.listView);
 InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {}.getType();
 artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 
 adapter = new ArtWorkAdapter();
 listView.setAdapter(adapter);
 
 } Loading data Parsing data Restoring persisted data
  52. What if… • the data source changes from file to

    an API • the response uses different serialization protocol: BSON, XML, protocol buffer… • the persistence changes to a db
  53. These requirement changes force modifications in the Activity, but it

    shouldn’t
  54. Activity Presenter Model User Interaction

  55. UNTESTABLE @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 
 setContentView(R.layout.activity_main);


    ListView listView = (ListView) findViewById(R.id.listView);
 InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {}.getType();
 artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 
 adapter = new ArtWorkAdapter();
 listView.setAdapter(adapter);
 
 }
  56. Refactor

  57. public class ArtWorkListActivity extends Activity{
 …
 @Override
 protected void onCreate(Bundle

    savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 ListView listView = (ListView)findViewById(R.id.listView);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 }
 …
 } public class ArtWorkListPresenter implements Presenter {
 
 @Override
 public void onCreate() { } …
 } InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {
 }.getType();
 artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 
 adapter = new ArtWorkAdapter();
 listView.setAdapter(adapter);
  58. public class ArtWorkListActivity extends Activity{
 …
 @Override
 protected void onCreate(Bundle

    savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 ListView listView = (ListView)findViewById(R.id.listView);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 }
 …
 } public class ArtWorkListPresenter implements Presenter {
 
 @Override
 public void onCreate() { } …
 } InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {
 }.getType();
 artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 
 adapter = new ArtWorkAdapter();
 listView.setAdapter(adapter);
  59. public class ArtWorkListActivity extends Activity{
 …
 @Override
 protected void onCreate(Bundle

    savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 ListView listView = (ListView)findViewById(R.id.listView);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 }
 …
 } public class ArtWorkListPresenter implements Presenter {
 
 @Override
 public void onCreate() { } …
 } InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {
 }.getType();
 artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 
 adapter = new ArtWorkAdapter();
 listView.setAdapter(adapter); Presenter presenter = new ArtWorkListPresenter(this); presenter.onCreate();
  60. Activity Presenter Model User Interaction

  61. public class ArtWorkListPresenter implements Presenter {
 
 @Override
 public void

    onCreate() {
 InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {
 }.getType();
 artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 
 }
 }
 adapter = new ArtWorkAdapter();
 listView.setAdapter(adapter); public interface Display {
 public void setAdapter(List<ArtWork> artWorks);
 }
  62. public class ArtWorkListPresenter implements Presenter {
 
 @Override
 public void

    onCreate() {
 InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {
 }.getType();
 artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 
 }
 }
 adapter = new ArtWorkAdapter();
 listView.setAdapter(adapter); View logic public interface Display {
 public void setAdapter(List<ArtWork> artWorks);
 }
  63. public class ArtWorkListPresenter implements Presenter {
 
 @Override
 public void

    onCreate() {
 InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {
 }.getType();
 artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 
 }
 }
 public class ArtWorkListActivity extends Activity implements Display {
 @Override
 public void setAdapter(List<ArtWork> artWorks) { } } adapter = new ArtWorkAdapter();
 listView.setAdapter(adapter); View logic public interface Display {
 public void setAdapter(List<ArtWork> artWorks);
 }
  64. public class ArtWorkListPresenter implements Presenter {
 
 @Override
 public void

    onCreate() {
 InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {
 }.getType();
 artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 
 }
 }
 public class ArtWorkListActivity extends Activity implements Display {
 @Override
 public void setAdapter(List<ArtWork> artWorks) { } } adapter = new ArtWorkAdapter();
 listView.setAdapter(adapter); public interface Display {
 public void setAdapter(List<ArtWork> artWorks);
 }
  65. public class ArtWorkListPresenter implements Presenter {
 
 @Override
 public void

    onCreate() {
 InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {
 }.getType();
 artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 
 }
 }
 public class ArtWorkListActivity extends Activity implements Display {
 @Override
 public void setAdapter(List<ArtWork> artWorks) { } } adapter = new ArtWorkAdapter();
 listView.setAdapter(adapter); artWorkList = artWorks; public interface Display {
 public void setAdapter(List<ArtWork> artWorks);
 }
  66. public class ArtWorkListPresenter implements Presenter {
 
 @Override
 public void

    onCreate() {
 InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {
 }.getType();
 artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 
 }
 }
 public class ArtWorkListActivity extends Activity implements Display {
 @Override
 public void setAdapter(List<ArtWork> artWorks) { } } adapter = new ArtWorkAdapter();
 listView.setAdapter(adapter); artWorkList = artWorks; public interface Display {
 public void setAdapter(List<ArtWork> artWorks);
 } public ArtWorkListPresenter(Display display) { this.display = display; }
  67. public class ArtWorkListPresenter implements Presenter {
 
 @Override
 public void

    onCreate() {
 InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {
 }.getType();
 artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 
 }
 }
 public class ArtWorkListActivity extends Activity implements Display {
 @Override
 public void setAdapter(List<ArtWork> artWorks) { } } adapter = new ArtWorkAdapter();
 listView.setAdapter(adapter); artWorkList = artWorks; display.setAdapter(artWorkList); public interface Display {
 public void setAdapter(List<ArtWork> artWorks);
 } public ArtWorkListPresenter(Display display) { this.display = display; }
  68. Activity Presenter Model User Interaction

  69. public class ArtWorkListPresenter implements Presenter {
 private final Display display;

    
 
 
 this.display = display; 
 }
 
 @Override
 public void onCreate() {
 display.setAdapter(artWorkList);
 
 }
 } InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {
 }.getType();
 List<ArtWork> artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 public ArtWorkListPresenter(Display display) {
  70. public class ArtWorkListPresenter implements Presenter {
 private final Display display;

    
 
 
 this.display = display; 
 }
 
 @Override
 public void onCreate() {
 display.setAdapter(artWorkList);
 
 }
 } Model logic InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {
 }.getType();
 List<ArtWork> artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 public ArtWorkListPresenter(Display display) {
  71. public class ArtWorkListPresenter implements Presenter {
 private final Display display;

    
 
 
 this.display = display; 
 }
 
 @Override
 public void onCreate() {
 display.setAdapter(artWorkList);
 
 }
 } public interface Model {
 List<ArtWork> getArtWorks();
 } InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {
 }.getType();
 List<ArtWork> artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 public ArtWorkListPresenter(Display display) {
  72. public class ArtWorkListPresenter implements Presenter {
 private final Display display;

    
 
 
 this.display = display; 
 }
 
 @Override
 public void onCreate() {
 display.setAdapter(artWorkList);
 
 }
 } public interface Model {
 List<ArtWork> getArtWorks();
 } public class ArtWorkModel implements Model {
 @Override
 public List<ArtWork> getArtWorks() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 }
 }
 InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {
 }.getType();
 List<ArtWork> artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 public ArtWorkListPresenter(Display display) {
  73. public class ArtWorkListPresenter implements Presenter {
 private final Display display;

    
 
 
 this.display = display; 
 }
 
 @Override
 public void onCreate() {
 display.setAdapter(artWorkList);
 
 }
 } public interface Model {
 List<ArtWork> getArtWorks();
 } public class ArtWorkModel implements Model {
 @Override
 public List<ArtWork> getArtWorks() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 }
 }
 InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {
 }.getType();
 List<ArtWork> artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 public ArtWorkListPresenter(Display display) {
  74. public class ArtWorkListPresenter implements Presenter {
 private final Display display;

    
 
 
 this.display = display; 
 }
 
 @Override
 public void onCreate() {
 display.setAdapter(artWorkList);
 
 }
 } public interface Model {
 List<ArtWork> getArtWorks();
 } public class ArtWorkModel implements Model {
 @Override
 public List<ArtWork> getArtWorks() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 }
 }
 InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {
 }.getType();
 List<ArtWork> artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 private final Model model; this.model = model; public ArtWorkListPresenter(Display display,Model model) {
  75. public class ArtWorkListPresenter implements Presenter {
 private final Display display;

    
 
 
 this.display = display; 
 }
 
 @Override
 public void onCreate() {
 display.setAdapter(artWorkList);
 
 }
 } public interface Model {
 List<ArtWork> getArtWorks();
 } public class ArtWorkModel implements Model {
 @Override
 public List<ArtWork> getArtWorks() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 }
 }
 InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {
 }.getType();
 List<ArtWork> artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 List<ArtWork> artWorkList = model.getArtWorks(); private final Model model; this.model = model; public ArtWorkListPresenter(Display display,Model model) {
  76. public class ArtWorkListPresenter implements Presenter {
 private final Display display;

    
 
 
 this.display = display; 
 }
 
 @Override
 public void onCreate() {
 display.setAdapter(artWorkList);
 
 }
 } public interface Model {
 List<ArtWork> getArtWorks();
 } public class ArtWorkModel implements Model {
 @Override
 public List<ArtWork> getArtWorks() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 }
 }
 InputStream stream = getResources().openRawResource(R.raw.artwork);
 Type listType = new TypeToken<List<ArtWork>>() {
 }.getType();
 List<ArtWork> artWorkList = new Gson().fromJson(new InputStreamReader(stream), listType);
 final SharedPreferences preferences = getSharedPreferences(getPackageName()
 , Context.MODE_PRIVATE);
 for (ArtWork artWork : artWorkList) {
 artWork.setRating(preferences.getFloat(PACKAGE + artWork.getId(), 0F));
 }
 List<ArtWork> artWorkList = model.getArtWorks(); return artWorkList; private final Model model; this.model = model; public ArtWorkListPresenter(Display display,Model model) {
  77. Activity Presenter Model User Interaction

  78. Add test to Activity II

  79. None
  80. public class ArtWorkListActivity extends Activity implements Display {
 … @Override


    protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 listView = (ListView) findViewById(R.id.listView);
 artWorkList = new ArrayList<ArtWork>();
 if (presenter == null) {
 presenter = new ArtWorkListPresenter(this,new ArtWorkModel());
 }
 presenter.onCreate();
 } … } public class ArtWorkListActivityTests extends ActivityUnitTestCase<ArtWorkListActivity> {
 
 public void testOnCreate() throws Exception {
 
 Intent launchIntent = new Intent(getInstrumentation()
 .getTargetContext(),ArtWorkListActivity.class); startActivity(launchIntent,null,null);
 activity=getActivity();
 
 activity.onCreate(new Bundle());
 
 }
 }
  81. public class ArtWorkListActivity extends Activity implements Display {
 … @Override


    protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 listView = (ListView) findViewById(R.id.listView);
 artWorkList = new ArrayList<ArtWork>();
 if (presenter == null) {
 presenter = new ArtWorkListPresenter(this,new ArtWorkModel());
 }
 presenter.onCreate();
 } … } public class ArtWorkListActivityTests extends ActivityUnitTestCase<ArtWorkListActivity> {
 
 public void testOnCreate() throws Exception {
 
 Intent launchIntent = new Intent(getInstrumentation()
 .getTargetContext(),ArtWorkListActivity.class); startActivity(launchIntent,null,null);
 activity=getActivity();
 
 activity.onCreate(new Bundle());
 
 }
 }
  82. public class ArtWorkListActivity extends Activity implements Display {
 … @Override


    protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 listView = (ListView) findViewById(R.id.listView);
 artWorkList = new ArrayList<ArtWork>();
 if (presenter == null) {
 presenter = new ArtWorkListPresenter(this,new ArtWorkModel());
 }
 presenter.onCreate();
 } … } public class ArtWorkListActivityTests extends ActivityUnitTestCase<ArtWorkListActivity> {
 
 public void testOnCreate() throws Exception {
 
 Intent launchIntent = new Intent(getInstrumentation()
 .getTargetContext(),ArtWorkListActivity.class); startActivity(launchIntent,null,null);
 activity=getActivity();
 
 activity.onCreate(new Bundle());
 
 }
 } public void setPresenter(Presenter presenter) { this.presenter = presenter; }
  83. public class ArtWorkListActivity extends Activity implements Display {
 … @Override


    protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 listView = (ListView) findViewById(R.id.listView);
 artWorkList = new ArrayList<ArtWork>();
 if (presenter == null) {
 presenter = new ArtWorkListPresenter(this,new ArtWorkModel());
 }
 presenter.onCreate();
 } … } public class ArtWorkListActivityTests extends ActivityUnitTestCase<ArtWorkListActivity> {
 
 public void testOnCreate() throws Exception {
 
 Intent launchIntent = new Intent(getInstrumentation()
 .getTargetContext(),ArtWorkListActivity.class); startActivity(launchIntent,null,null);
 activity=getActivity();
 
 activity.onCreate(new Bundle());
 
 }
 } public void setPresenter(Presenter presenter) { this.presenter = presenter; } presenter = mock(Presenter.class); activity.setPresenter(presenter); verify(presenter, only()).onCreate();
  84. public class ArtWorkListActivity extends Activity implements Display {
 … @Override


    protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 listView = (ListView) findViewById(R.id.listView);
 artWorkList = new ArrayList<ArtWork>();
 if (presenter == null) {
 presenter = new ArtWorkListPresenter(this,new ArtWorkModel());
 }
 presenter.onCreate();
 } … } public class ArtWorkListActivityTests extends ActivityUnitTestCase<ArtWorkListActivity> {
 
 public void testOnCreate() throws Exception {
 
 Intent launchIntent = new Intent(getInstrumentation()
 .getTargetContext(),ArtWorkListActivity.class); startActivity(launchIntent,null,null);
 activity=getActivity();
 
 activity.onCreate(new Bundle());
 
 }
 } public void setPresenter(Presenter presenter) { this.presenter = presenter; } presenter = mock(Presenter.class); activity.setPresenter(presenter); verify(presenter, only()).onCreate();
  85. public void testOnCreate() throws Exception {
 PresenterMock presenter = new

    PresenterMock();
 Intent launchIntent = new Intent( getInstrumentation().getTargetContext(), ArtWorkListActivity.class);
 startActivity(launchIntent, null, null);
 activity = getActivity();
 activity.setPresenter(presenter);
 activity.onCreate(new Bundle());
 assertTrue(presenter.isCalled);
 }
 
 private class PresenterMock implements Presenter {
 boolean isCalled = false;
 
 @Override
 public void onCreate() {
 isCalled = true;
 }
 } public void testOnCreate() throws Exception {
 presenter = mock(Presenter.class);
 Intent launchIntent = new Intent(getInstrumentation().getTargetContext(), ArtWorkListActivity.class);
 startActivity(launchIntent,null,null);
 activity = getActivity();
 activity.setPresenter(presenter);
 activity.onCreate(new Bundle());
 verify(presenter, only()).onCreate();
 }
  86. public class ArtWorkListActivity extends Activity implements Display {
 private Presenter

    presenter;
 List<ArtWork> artWorkList;
 ArtWorkAdapter adapter;
 ListView listView; … @Override
 public void setAdapter(List<ArtWork> artWorks) {
 artWorkList = artWorks;
 adapter = new ArtWorkAdapter();
 listView.setAdapter(adapter);
 } … } public class ArtWorkListActivityTests extends
 ActivityUnitTestCase<ArtWorkListActivity> {
 private static final String ANY_ID = "15224484";
 private static final List<ArtWork> ARTWORKS = new ArrayList<ArtWork>() {
 {
 add(new ArtWork(ArtWork.QUOTE, ANY_ID));
 }
 };
 private ArtWorkListActivity activity;
 …
 public void testSetAdapter() throws Exception {
 
 
 
 
 
 }
 
 } private private private
  87. public class ArtWorkListActivity extends Activity implements Display {
 private Presenter

    presenter;
 List<ArtWork> artWorkList;
 ArtWorkAdapter adapter;
 ListView listView; … @Override
 public void setAdapter(List<ArtWork> artWorks) {
 artWorkList = artWorks;
 adapter = new ArtWorkAdapter();
 listView.setAdapter(adapter);
 } … } public class ArtWorkListActivityTests extends
 ActivityUnitTestCase<ArtWorkListActivity> {
 private static final String ANY_ID = "15224484";
 private static final List<ArtWork> ARTWORKS = new ArrayList<ArtWork>() {
 {
 add(new ArtWork(ArtWork.QUOTE, ANY_ID));
 }
 };
 private ArtWorkListActivity activity;
 …
 public void testSetAdapter() throws Exception {
 
 
 
 
 
 }
 
 }
  88. public class ArtWorkListActivity extends Activity implements Display {
 private Presenter

    presenter;
 List<ArtWork> artWorkList;
 ArtWorkAdapter adapter;
 ListView listView; … @Override
 public void setAdapter(List<ArtWork> artWorks) {
 artWorkList = artWorks;
 adapter = new ArtWorkAdapter();
 listView.setAdapter(adapter);
 } … } public class ArtWorkListActivityTests extends
 ActivityUnitTestCase<ArtWorkListActivity> {
 private static final String ANY_ID = "15224484";
 private static final List<ArtWork> ARTWORKS = new ArrayList<ArtWork>() {
 {
 add(new ArtWork(ArtWork.QUOTE, ANY_ID));
 }
 };
 private ArtWorkListActivity activity;
 …
 public void testSetAdapter() throws Exception {
 
 
 
 
 
 }
 
 } activity.setAdapter(ARTWORKS);
 Adapter adapter = activity.listView.getAdapter();
 assertEquals(activity.artWorkList, ARTWORKS);
 assertEquals(adapter.getCount(), ARTWORKS.size());
 assertEquals(adapter.getItem(0), ARTWORKS.get(0));
  89. public class ArtWorkListPresenter implements Presenter {
 private final Display display;

    private final Model model; public ArtWorkListPresenter(Display display,Model model) {
 this.model = model; 
 this.display = display;
 }
 
 @Override
 public void onCreate() {
 List<ArtWork> artWorkList = model.getArtWorks(); display.setAdapter(artWorkList); } } public class ArtWorkPresenterTests extends TestCase {
 private Display display;
 private Model model;
 private Presenter presenter;
 
 @Override
 public void setUp() throws Exception {
 super.setUp();
 display = Mockito.mock(Display.class);
 model = Mockito.mock(Model.class); Mockito.when(model.getArtWorks()).thenReturn(ARTWORKS); presenter = new ArtWorkListPresenter(display, model);
 }
 
 public void testOnCreate() throws Exception { presenter.onCreate();
 verify(display, only()).setAdapter(ARTWORKS);
 }
 }
  90. Add new features

  91. Get the code https://github.com/rallat/libraryofalexandria

  92. Excuses • Dex method limit: this is not even a

    thing with multidex :D • Android is hard to test: new routines are never easy is a matter of start the routine. It’s is definitely possible and easy with dagger and robolectric. • Optimization: one hat at a time, first do it, refactor it and then optimize it
  93. None
  94. None
  95. Thanks @flipper83 @macarse @fernando_cejas @wendi_ @mbe @pedro_g_s @lientm @tsmith

  96. Bibliography • Clean code • Working Effectively with Legacy Code

    • Refactoring: Improving design of existing code • Android Application Testing Guide • Android Testing fundamentals • Writing testable code by Misko Hevery
  97. Questions? #legacyAndroid