Android Design
Patterns
Godfrey Nolan
Android Application Architecture (Android Dev Summit) https://www.youtube.com/watch?v=BlkJzgjzL0c
What is Your Goal?
Scalable
Maintainable
Testing
What is Your Goal?
Scalable
Add new features quickly
Maintainable
No spaghetti code
Don't cross the streams
Testing
Easy to mock
Why?
Easier to add new features
Easier to understand
Easier to police
Make our life easier
Make Unit Testing easier
How do we get there?
https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)
Classic Android
View Model
https://github.com/konmik/konmik.github.io/wiki/Introduction-to-Model-View-Presenter-on-Android
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;
}
}
}
Classic Android
Pros
Better the devil you know
Cons
Activities and Fragments quickly become large
Painful to make changes or add new features
All the logic in Activities, unit testing is impossible
Classic Android is not MVC
MVC
https://medium.com/@tinmegali/model-view-presenter-mvp-in-android-part-1-441bfd7998fe#.d19x8cido
http://androidexample.com/Use_MVC_Pattern_To_Create_Very_Basic_Shopping_Cart__-_Android_Example
package com.androidexample.mvc;
//imports
public class FirstScreen extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.firstscreen);
final LinearLayout lm = (LinearLayout) findViewById(R.id.linearMain);
final Button secondBtn = (Button) findViewById(R.id.second);
//Get Global Controller Class object (see application tag in AndroidManifest.xml)
final Controller aController = (Controller) getApplicationContext();
/* ........ */
//Product arraylist size
int ProductsSize = aController.getProductsArraylistSize();
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
/******** Dynamically create view elements - Start **********/
for(int j=0;j< ProductsSize;j++)
{
// Get probuct data from product data arraylist
String pName = aController.getProducts(j).getProductName();
int pPrice = aController.getProducts(j).getProductPrice();
// Create LinearLayout to view elemnts
LinearLayout ll = new LinearLayout(this);
ll.setOrientation(LinearLayout.HORIZONTAL);
TextView product = new TextView(this);
product.setText(" "+pName+" ");
//Add textView to LinearLayout
ll.addView(product);
TextView price = new TextView(this);
price.setText(" $"+pPrice+" ");
//Add textView to LinearLayout
ll.addView(price);
final Button btn = new Button(this);
btn.setId(j+1);
btn.setText("Add To Cart");
// set the layoutParams on the button
btn.setLayoutParams(params);
package com.androidexample.mvc;
import java.util.ArrayList;
import android.app.Application;
public class Controller extends Application{
private ArrayList<ModelProducts> myProducts = new ArrayList<ModelProducts>();
private ModelCart myCart = new ModelCart();
public ModelProducts getProducts(int pPosition) {
return myProducts.get(pPosition);
}
public void setProducts(ModelProducts Products) {
myProducts.add(Products);
}
public ModelCart getCart() {
return myCart;
}
public int getProductsArraylistSize() {
return myProducts.size();
}
}
package com.androidexample.mvc;
public class ModelProducts {
private String productName;
private String productDesc;
private int productPrice;
public ModelProducts(String productName,String productDesc,int productPrice) {
this.productName = productName;
this.productDesc = productDesc;
this.productPrice = productPrice;
}
public String getProductName() {
return productName;
}
public String getProductDesc() {
return productDesc;
}
public int getProductPrice() {
return productPrice;
}
}
MVC
Pros
No business logic in UI
Easier to unit test
Cons
Doesn't scale, separates UI but not model
Controller often grows too big
https://github.com/konmik/konmik.github.io/wiki/Introduction-to-Model-View-Presenter-on-Android
MVP
Model-View-Presenter
https://speakerdeck.com/rallat/android-development-like-a-pro
MVP
Increases separation of concerns into 3 layers
Passive View - Render logic
Presenter - Handle User events (Proxy)
Model - Business logic
https://github.com/googlesamples/android-architecture/wiki/Samples-at-a-glance
https://www.linkedin.com/pulse/mvc-mvp-mvvm-architecture-patterns-shashank-gupta
https://github.com/erikcaffrey/Android-Spotify-MVP
public interface ArtistsMvpView extends MvpView{
void showLoading();
void hideLoading();
void showArtistNotFoundMessage();
void showConnectionErrorMessage();
void renderArtists(List<Artist> artists);
void launchArtistDetail(Artist artist);
}
public class ArtistsPresenter implements Presenter<ArtistsMvpView>, ArtistCallback {
private ArtistsMvpView artistsMvpView;
private ArtistsInteractor artistsInteractor;
public ArtistsPresenter() {
}
@Override public void setView(ArtistsMvpView view) {
if (view == null) throw new IllegalArgumentException("You can't set a null view");
artistsMvpView = view;
artistsInteractor = new ArtistsInteractor(artistsMvpView.getContext());
}
@Override public void detachView() {
artistsMvpView = null;
}
public void onSearchArtist(String string) {
artistsMvpView.showLoading();
artistsInteractor.loadDataFromApi(string, this);
}
public void launchArtistDetail(Artist artist) {
artistsMvpView.launchArtistDetail(artist);
}
//.....
}
public class ArtistsInteractor {
SpotifyService mSpotifyService;
SpotifyApp mSpotifyApp;
public ArtistsInteractor(Context context) {
this.mSpotifyApp = SpotifyApp.get(context);
this.mSpotifyService = mSpotifyApp.getSpotifyService();
}
public void loadDataFromApi(String query, ArtistCallback artistCallback) {
mSpotifyService.searchArtist(query)
.subscribeOn(mSpotifyApp.SubscribeScheduler())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(artistsSearch -> onSuccess(artistsSearch, artistCallback),
throwable -> onError(throwable, artistCallback));
}
public class MainActivity extends Activity implements MainView, AdapterView.OnItemClickListener {
private ListView listView;
private ProgressBar progressBar;
private MainPresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.list);
listView.setOnItemClickListener(this);
progressBar = (ProgressBar) findViewById(R.id.progress);
presenter = new MainPresenterImpl(this);
}
@Override public void setItems(List<String> items) {
listView.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, items));
}
@Override public void showMessage(String message) {
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
}
@Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
presenter.onItemClicked(position);
}
}
package com.antonioleiva.mvpexample.app.main;
public interface MainPresenter {
void onResume();
void onItemClicked(int position);
void onDestroy();
}
public class MainPresenterImpl implements MainPresenter, FindItemsInteractor.OnFinishedListener {
private MainView mainView;
private FindItemsInteractor findItemsInteractor;
public MainPresenterImpl(MainView mainView) {
this.mainView = mainView;
findItemsInteractor = new FindItemsInteractorImpl();
}
@Override public void onResume() {
if (mainView != null) {
mainView.showProgress();
}
findItemsInteractor.findItems(this);
}
@Override public void onItemClicked(int position) {
if (mainView != null) {
mainView.showMessage(String.format("Position %d clicked", position + 1));
}
}
@Override public void onDestroy() {
mainView = null;
}
@Override public void onFinished(List<String> items) {
if (mainView != null) {
mainView.setItems(items);
mainView.hideProgress();
}
}
}
public interface MainView {
void showProgress();
void hideProgress();
void setItems(List<String> items);
void showMessage(String message);
}
http://antonioleiva.com/mvp-android/
MVP Testing
View
Test render logic and interaction with presenter,
mock Presenter.
Presenter
Test that view events invoke the right model
method. Mock both View and Model.
Model
Test the business logic, mock the data source and
Presenter.
MVP
Pros
Complex Tasks split into simpler tasks
Smaller objects, less bugs, easier to debug
Testable
Cons
BoilerPlate to wire the layers.
Model can’t be reused, tied to specific use case.
View and Presenter are tied to data objects since
they share the same type of object with the Model.
https://speakerdeck.com/rallat/androiddevlikeaprodroidconsf
MVVM
Microsoft Pattern
Removes UI code from Activities/Fragments
View has no knowledge of model
Data Binding = Bind ViewModel to Layout
Goodbye Presenter, hello ViewModel
http://tech.vg.no/2015/07/17/android-databinding-goodbye-presenter-hello-viewmodel/
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/password_block"
android:id="@+id/email_block"
android:visibility="@{data.emailBlockVisibility}">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Email:"
android:minWidth="100dp"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="textEmailAddress"
android:ems="10"
android:id="@+id/email"/>
</LinearLayout>
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_main, container, false);
mBinding = FragmentMainBinding.bind(view);
mViewModel = new MainModel(this, getResources());
mBinding.setData(mViewModel);
attachButtonListener();
return view;
}
public void updateDependentViews() {
if (isExistingUserChecked.get()) {
emailBlockVisibility.set(View.GONE);
loginOrCreateButtonText.set(mResources.getString(R.string.log_in));
}
else {
emailBlockVisibility.set(View.VISIBLE);
loginOrCreateButtonText.set(mResources.getString(R.string.create_user));
}
}
http://tech.vg.no/2015/07/17/android-databinding-goodbye-presenter-hello-viewmodel/
MVVM
Pros
First Party Library
Compile time checking
Presentation layer in XML
Testable
Less code, no more Butterknife
Cons
Data Binding isn't always appropriate
Android Studio integration was flaky
Reactive - RxJava
Not really an architecture
Used in many other architectures
Event based Publish / Subscribe
public void doLargeComputation(
final IComputationListener listener,
final OtherParams params) {
Subscription subscription = doLargeComputationCall(params)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<Result>>>() {
@Override public void onNext(final List<Result> results) {
listener.doLargeComputationComplete(results);
}
@Override public void onCompleted() {}
@Override public void onError(final Throwable t) {
listener.doLargeComputationFailed(t);
}
}
);
}
private Observable<List<Result>> doLargeComputationCall(final OtherParams params) {
return Observable.defer(new Func0<Observable<List<Result>>>() {
@Override public Observable<List<Result>> call() {
List<Result> results = doTheRealLargeComputation(params);
if (results != null && 0 < results.size()) {
return Observable.just(results);
}
return Observable.error(new ComputationException("Could not do the large computation"));
}
}
);
}
MVC w/RxJava
https://github.com/yigit/dev-summit-architecture-demo
Clean Architecture
Uncle Bob
Much better decoupling, better reusabiity
Lots more layers
https://github.com/rallat/EffectiveAndroid
Clean Architecture is...
Independent of Frameworks
Testable
Independent of UI
Independent of Database
Independent of any External Agency
Decoupled
Clean Architecture
https://github.com/rallat/EffectiveAndroid
Clean Architecture (Kotlin)
https://github.com/pardom/CleanNews
tearDown()
Act Early
Trust but Verify
Know what you're getting into
YAGNI
Repos
MVC: http://androidexample.com/Use_MVC_Pattern_To_Create_Very_Basic_Shopping_Cart__-_Android_Example
MVC-Reactive: https://github.com/yigit/dev-summit-architecture-demo
MVP: https://github.com/antoniolg/androidmvp
MVVM: https://github.com/ivacf/archi
Clean: https://github.com/rallat/EffectiveAndroid
Contact Details
godfrey@riis.com
@godfreynolan
slideshare.com/godfreynolan

Android Design Patterns

  • 1.
  • 2.
    Android Application Architecture(Android Dev Summit) https://www.youtube.com/watch?v=BlkJzgjzL0c
  • 3.
    What is YourGoal? Scalable Maintainable Testing
  • 4.
    What is YourGoal? Scalable Add new features quickly Maintainable No spaghetti code Don't cross the streams Testing Easy to mock
  • 5.
    Why? Easier to addnew features Easier to understand Easier to police Make our life easier Make Unit Testing easier
  • 6.
    How do weget there? https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)
  • 8.
  • 9.
  • 11.
    package alexandria.israelferrer.com.libraryofalexandria; import android.app.Activity; importandroid.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; } } }
  • 12.
    Classic Android Pros Better thedevil you know Cons Activities and Fragments quickly become large Painful to make changes or add new features All the logic in Activities, unit testing is impossible Classic Android is not MVC
  • 13.
  • 15.
  • 16.
    package com.androidexample.mvc; //imports public classFirstScreen extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.firstscreen); final LinearLayout lm = (LinearLayout) findViewById(R.id.linearMain); final Button secondBtn = (Button) findViewById(R.id.second); //Get Global Controller Class object (see application tag in AndroidManifest.xml) final Controller aController = (Controller) getApplicationContext(); /* ........ */ //Product arraylist size int ProductsSize = aController.getProductsArraylistSize(); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); /******** Dynamically create view elements - Start **********/ for(int j=0;j< ProductsSize;j++) { // Get probuct data from product data arraylist String pName = aController.getProducts(j).getProductName(); int pPrice = aController.getProducts(j).getProductPrice(); // Create LinearLayout to view elemnts LinearLayout ll = new LinearLayout(this); ll.setOrientation(LinearLayout.HORIZONTAL); TextView product = new TextView(this); product.setText(" "+pName+" "); //Add textView to LinearLayout ll.addView(product); TextView price = new TextView(this); price.setText(" $"+pPrice+" "); //Add textView to LinearLayout ll.addView(price); final Button btn = new Button(this); btn.setId(j+1); btn.setText("Add To Cart"); // set the layoutParams on the button btn.setLayoutParams(params);
  • 17.
    package com.androidexample.mvc; import java.util.ArrayList; importandroid.app.Application; public class Controller extends Application{ private ArrayList<ModelProducts> myProducts = new ArrayList<ModelProducts>(); private ModelCart myCart = new ModelCart(); public ModelProducts getProducts(int pPosition) { return myProducts.get(pPosition); } public void setProducts(ModelProducts Products) { myProducts.add(Products); } public ModelCart getCart() { return myCart; } public int getProductsArraylistSize() { return myProducts.size(); } } package com.androidexample.mvc; public class ModelProducts { private String productName; private String productDesc; private int productPrice; public ModelProducts(String productName,String productDesc,int productPrice) { this.productName = productName; this.productDesc = productDesc; this.productPrice = productPrice; } public String getProductName() { return productName; } public String getProductDesc() { return productDesc; } public int getProductPrice() { return productPrice; } }
  • 19.
    MVC Pros No business logicin UI Easier to unit test Cons Doesn't scale, separates UI but not model Controller often grows too big
  • 20.
  • 21.
  • 23.
    MVP Increases separation ofconcerns into 3 layers Passive View - Render logic Presenter - Handle User events (Proxy) Model - Business logic
  • 24.
  • 25.
  • 26.
  • 27.
    public interface ArtistsMvpViewextends MvpView{ void showLoading(); void hideLoading(); void showArtistNotFoundMessage(); void showConnectionErrorMessage(); void renderArtists(List<Artist> artists); void launchArtistDetail(Artist artist); } public class ArtistsPresenter implements Presenter<ArtistsMvpView>, ArtistCallback { private ArtistsMvpView artistsMvpView; private ArtistsInteractor artistsInteractor; public ArtistsPresenter() { } @Override public void setView(ArtistsMvpView view) { if (view == null) throw new IllegalArgumentException("You can't set a null view"); artistsMvpView = view; artistsInteractor = new ArtistsInteractor(artistsMvpView.getContext()); } @Override public void detachView() { artistsMvpView = null; } public void onSearchArtist(String string) { artistsMvpView.showLoading(); artistsInteractor.loadDataFromApi(string, this); } public void launchArtistDetail(Artist artist) { artistsMvpView.launchArtistDetail(artist); } //..... } public class ArtistsInteractor { SpotifyService mSpotifyService; SpotifyApp mSpotifyApp; public ArtistsInteractor(Context context) { this.mSpotifyApp = SpotifyApp.get(context); this.mSpotifyService = mSpotifyApp.getSpotifyService(); } public void loadDataFromApi(String query, ArtistCallback artistCallback) { mSpotifyService.searchArtist(query) .subscribeOn(mSpotifyApp.SubscribeScheduler()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(artistsSearch -> onSuccess(artistsSearch, artistCallback), throwable -> onError(throwable, artistCallback)); }
  • 29.
    public class MainActivityextends Activity implements MainView, AdapterView.OnItemClickListener { private ListView listView; private ProgressBar progressBar; private MainPresenter presenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); listView = (ListView) findViewById(R.id.list); listView.setOnItemClickListener(this); progressBar = (ProgressBar) findViewById(R.id.progress); presenter = new MainPresenterImpl(this); } @Override public void setItems(List<String> items) { listView.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, items)); } @Override public void showMessage(String message) { Toast.makeText(this, message, Toast.LENGTH_LONG).show(); } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { presenter.onItemClicked(position); } } package com.antonioleiva.mvpexample.app.main; public interface MainPresenter { void onResume(); void onItemClicked(int position); void onDestroy(); } public class MainPresenterImpl implements MainPresenter, FindItemsInteractor.OnFinishedListener { private MainView mainView; private FindItemsInteractor findItemsInteractor; public MainPresenterImpl(MainView mainView) { this.mainView = mainView; findItemsInteractor = new FindItemsInteractorImpl(); } @Override public void onResume() { if (mainView != null) { mainView.showProgress(); } findItemsInteractor.findItems(this); } @Override public void onItemClicked(int position) { if (mainView != null) { mainView.showMessage(String.format("Position %d clicked", position + 1)); } } @Override public void onDestroy() { mainView = null; } @Override public void onFinished(List<String> items) { if (mainView != null) { mainView.setItems(items); mainView.hideProgress(); } } } public interface MainView { void showProgress(); void hideProgress(); void setItems(List<String> items); void showMessage(String message); } http://antonioleiva.com/mvp-android/
  • 30.
    MVP Testing View Test renderlogic and interaction with presenter, mock Presenter. Presenter Test that view events invoke the right model method. Mock both View and Model. Model Test the business logic, mock the data source and Presenter.
  • 31.
    MVP Pros Complex Tasks splitinto simpler tasks Smaller objects, less bugs, easier to debug Testable Cons BoilerPlate to wire the layers. Model can’t be reused, tied to specific use case. View and Presenter are tied to data objects since they share the same type of object with the Model. https://speakerdeck.com/rallat/androiddevlikeaprodroidconsf
  • 32.
    MVVM Microsoft Pattern Removes UIcode from Activities/Fragments View has no knowledge of model Data Binding = Bind ViewModel to Layout Goodbye Presenter, hello ViewModel
  • 33.
  • 35.
    <LinearLayout android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/password_block" android:id="@+id/email_block" android:visibility="@{data.emailBlockVisibility}"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="Email:" android:minWidth="100dp"/> <EditText android:layout_width="wrap_content" android:layout_height="wrap_content" android:inputType="textEmailAddress" android:ems="10" android:id="@+id/email"/> </LinearLayout> @Override public View onCreateView(LayoutInflaterinflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_main, container, false); mBinding = FragmentMainBinding.bind(view); mViewModel = new MainModel(this, getResources()); mBinding.setData(mViewModel); attachButtonListener(); return view; } public void updateDependentViews() { if (isExistingUserChecked.get()) { emailBlockVisibility.set(View.GONE); loginOrCreateButtonText.set(mResources.getString(R.string.log_in)); } else { emailBlockVisibility.set(View.VISIBLE); loginOrCreateButtonText.set(mResources.getString(R.string.create_user)); } } http://tech.vg.no/2015/07/17/android-databinding-goodbye-presenter-hello-viewmodel/
  • 36.
    MVVM Pros First Party Library Compiletime checking Presentation layer in XML Testable Less code, no more Butterknife Cons Data Binding isn't always appropriate Android Studio integration was flaky
  • 37.
    Reactive - RxJava Notreally an architecture Used in many other architectures Event based Publish / Subscribe
  • 38.
    public void doLargeComputation( finalIComputationListener listener, final OtherParams params) { Subscription subscription = doLargeComputationCall(params) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<List<Result>>>() { @Override public void onNext(final List<Result> results) { listener.doLargeComputationComplete(results); } @Override public void onCompleted() {} @Override public void onError(final Throwable t) { listener.doLargeComputationFailed(t); } } ); } private Observable<List<Result>> doLargeComputationCall(final OtherParams params) { return Observable.defer(new Func0<Observable<List<Result>>>() { @Override public Observable<List<Result>> call() { List<Result> results = doTheRealLargeComputation(params); if (results != null && 0 < results.size()) { return Observable.just(results); } return Observable.error(new ComputationException("Could not do the large computation")); } } ); }
  • 39.
  • 40.
    Clean Architecture Uncle Bob Muchbetter decoupling, better reusabiity Lots more layers
  • 41.
  • 42.
    Clean Architecture is... Independentof Frameworks Testable Independent of UI Independent of Database Independent of any External Agency Decoupled
  • 44.
  • 45.
  • 47.
    tearDown() Act Early Trust butVerify Know what you're getting into YAGNI
  • 48.
    Repos MVC: http://androidexample.com/Use_MVC_Pattern_To_Create_Very_Basic_Shopping_Cart__-_Android_Example MVC-Reactive: https://github.com/yigit/dev-summit-architecture-demo MVP:https://github.com/antoniolg/androidmvp MVVM: https://github.com/ivacf/archi Clean: https://github.com/rallat/EffectiveAndroid
  • 49.