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

How to Survive a Legacy Code Apocalypse on Android

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

rallat

November 22, 2014
Tweet

More Decks by rallat

Other Decks in Technology

Transcript

  1. 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”
  2. SOLID • Single Responsibility • Open-Close • Liskov substitution •

    Interface segregation • Dependency inversion Read OOD principles in this essay by Robert C. Martin
  3. • 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.
  4. 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;
 }
 
 }
 }

  5. 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);
 

  6. 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 {
 

  7. 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) {

  8. }
 
 @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;
 

  9. 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() {
 

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

  11. @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);
 
 }
  12. @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);
 
 }
  13. @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
  14. @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
  15. @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
  16. @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
  17. @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
  18. 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
  19. 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);
 
 }
  20. 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);
  21. 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);
  22. 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();
  23. 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);
 }
  24. 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);
 }
  25. 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);
 }
  26. 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);
 }
  27. 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);
 }
  28. 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; }
  29. 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; }
  30. 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) {
  31. 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) {
  32. 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) {
  33. 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) {
  34. 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) {
  35. 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) {
  36. 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) {
  37. 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) {
  38. 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());
 
 }
 }
  39. 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());
 
 }
 }
  40. 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; }
  41. 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();
  42. 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();
  43. 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();
 }
  44. 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
  45. 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 {
 
 
 
 
 
 }
 
 }
  46. 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));
  47. 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);
 }
 }
  48. 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
  49. 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