SlideShare una empresa de Scribd logo
1 de 49
Descargar para leer sin conexión
Using MVP in the Skroutz
application
Chris Mpitzios
MVC approach and
Skroutz Application facts
Skroutz is a network intensive application. Almost all of its screens require communication with Skroutz API in order to fetch and display relevant data.
● 11% responsible for static content displaying.
● 89% responsible for network communication
● Only portrait orientation supported
● Average LOC for active presentation elements ≈500 lines
(excluding placeholder activities)
● All network related screens follow the same view state pattern:
○ Load in the background
○ Display loading view
○ Display fetched data or an error message if needed
27
Screens
Skroutz App with MVC
Expectations from MVC approach:
Good separation of concerns
Clean, reusable, testable code
Minimizing of spaghetti effect
Keep code complexity low
MVC does not fit Android
Skroutz App with MVC
Review of MVC approach:
View just structures displayed interface
High complexity - Spaghetti code - hard to test code-
Unmaintainable state
RESULT
One god object responsible for everything
MVC-View responsibilities move to Controller
Controller should: data bind, manage
animations, user interactions ……..
Additional requirements
Eliminate memory leaks
Support orientation changes
Define abstractions for repeated view flows
(Load-Content-Error)
Separates presentation layer from the logic
Presenter:
● fetches, formats, delivers data to View
● instruct View for UI actions according to data
● keeps a reference to both View and Model
View :
● completely passive
● displays data
● cannot access model
MVP to the rescue
Controller is part of the view
View-Presenter -> one-to-one relationship
Multiple Presenters for complex Views
Next alternative
Let's implement MVP
How? Custom implementation or use an existing library?
Check available implementations before reinventing the wheel
Most popular implementations at the time were Mosby and Nucleus
Mosby vs Nucleus
Mosby vs Nucleus
public interface MainActivityView extends MvpView {
void showLoading();
void setData(API.Item[] response);
void showContent();
void showError(Throwable throwable);
}
public class MainPresenter extends MvpBasePresenter<MainActivityView> {
// Public
public void loadData() {
return App.getRestAdapterInstance().getItems(mApiCallback);
}
private final Callback<API.Item[]> mApiCallback = new Callback<API.Item[]>()
{
@Override
public void failure(final Throwable throwable) {
if (isViewAttached())
getView().showError(throwable);
}
@Override
public void success(API.Item[] response) {
if (isViewAttached())
getView().setData(response);
}
};
}
Mosby Presenter implementation:
MainActivityView interface:
public class MainPresenter extends RxPresenter<MainActivity> {
private static final int REQUEST_ITEMS_RESTARTABLE_ID = 1;
@Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
restartableLatestCache(REQUEST_ITEMS_RESTARTABLE_ID,
new Func0<Observable<ServerAPI.Response>>() {
@Override
public Observable<ServerAPI.Response> call() {
return App.getServerAPI()
.getItems(“request Argument”)
.observeOn(AndroidSchedulers.mainThread());
}
},
new Action2<MainActivity, ServerAPI.Response>() {
@Override
public void call(MainActivity activity, ServerAPI.Response response) {
activity.setData(response.items, “requestArgument”);
}
},
new Action2<MainActivity, Throwable>() {
@Override
public void call(MainActivity activity, Throwable throwable) {
activity.showError(throwable);
}
});
if (savedState == null)
start(REQUEST_ITEMS_RESTARTABLE_ID);
}
// Public
public void loadData() {
start(REQUEST_ITEMS_RESTARTABLE_ID);
}
}
Nucleus Presenter implementation:
Mosby vs Nucleus
An important difference
Nucleus presenter had an onCreate() method which means that some kind of lifecycle exists
Mosby on the other hand does not retain Presenters in any way
Mosby vs Nucleus
Main concepts and differences
Custom annotations to inject presenters
Nucleus:Mosby:
RxJava/observables used and mandatory
No separate interface for view actions
Presenters have a primitive lifecycle
All running tasks reattached automatically
Presenters can survive an activity recreation
Based on delegation so one can use delegates
to integrate it in a custom way
Presenters must be explicitly provided
MvpView as API for view related methods
Presenters independent from view’s lifecycle
Previous running tasks not restored
Presenters are not retained in any way
Based on delegation so one can use delegates
to integrate it in a custom way
ViewState provided to restore UI state
RxJava/observables not mandatory
Mosby was more documented and
embraced from the community
Skroutz App Categories screen
Main concepts
One placeholder activity hosting Categories fragment
Network call to API’s Categories endpoint
Endpoint requires/provides paging
Retrofit (and OkHttp) used
Both orientations supported
One RecyclerView to display data
CategoriesFragment MVC implementation
public class CategoriesFragment extends Fragment implements AdapterView.OnItemClickListener {
private AbstractRecyclerViewAdapter<Category> mAdapter;
private Paginator mPaginator = new Paginator();
private boolean mIsAlreadyLoading= false;
@Override
public void onActivityCreated(@Nullable final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mCategory = getActivity().getIntent().getParcelableExtra(KEY_BUNDLE_CATEGORY);
loadData();
}
@Override
public void onDestroy() {
super.onDestroy();
mApiCallback.invalidate();
}
private void setData(List<Category> list) {
if (list == null){
showError(SKError.noResultsError());
return;
}
mAdapter.addData(list);
mAdapter.notifyDataSetChanged();
showError(null);
}
private void showLoading() {............}
private void showContent() {............}
private void showError(SKError error) {............}
private void loadData() {
if (mPaginator.isPagesCompleted())
return;
if (mIsAlreadyLoading)
return;
mIsAlreadyLoading = true;
showLoading();
Category.fetchSubCategories(mCategory.id,
mCurrentPaginator.page + 1,
mApiCallback);
}
private final SKCallback<ResponseCategories> mApiCallback = new
SKCallback<ResponseCategories>() {
@Override
public void failure(final SKError error) {
mIsAlreadyLoading = false;
showError(error);
}
@Override
public void success(ResponseCategories response) {
mIsAlreadyLoading = false;
mPaginator = response.meta.paginator;
setData(response.categories);
showContent()
}
};
CategoriesFragment MVC implementation review
Unneeded complexity, no separation of concerns!
Everything takes place in Fragment (View). Network calls, interface handling..
Paginator (part of business logic) included and preserved in View
Memory leaks and random crashes detected
UI flow for orientation change was broken
View is not dumb but absolutely stateful
Mosby implementation
CategoriesFragment Mosby implementation
public class CategoriesFragment extends MvpFragment<CategoriesView, CategoriesPresenter,
Category> implements CategoriesView{
private AbstractRecyclerViewAdapter<Category> mAdapter;
@Override
public void onActivityCreated(@Nullable final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mCategory = getActivity().getIntent().getParcelableExtra(KEY_BUNDLE_CATEGORY);
if (savedInstanceState != null) {
presenter.onRestoreInstanceState(savedInstanceState);
if (getSavedDataFromBundle() != null) {
setData(getSavedDataFromBundle());
return;
}
}
loadData();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
presenter.onSaveInstanceState(outState);
outState.putParcelableArrayList(KEY_BUNDLE_DATA, (ArrayList<Category>) mAdapter.getData());
}
@Override
public CategoriesPresenter createPresenter() {
return new CategoriesPresenter();
}
@Override
public void loadData() {
presenter.loadData(mCategory.id);
}
@Override
public void setData(List<Category> list) {
mAdapter.addData(list);
mAdapter.notifyDataSetChanged();
showError(null);
}
@Override
public void showLoading() {............}
@Override
public void showContent() {............}
@Override
public void showError(SKError error) {............}
CategoryPresenter and CategoryView - Mosby
public class CategoryPresenter extends MvpBasePresenter<CategoryView> {
// Attributes
private boolean mIsLoadingMore = false;
private Paginator mPaginator = new Paginator();
public void onRestoreInstanceState(Bundle savedInstanceState) {
mPaginator = savedInstanceState.getParcelable(KEY_BUNDLE_PAGINATOR);
}
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putParcelable(KEY_BUNDLE_PAGINATOR, mPaginator);
}
public void loadData(final int categoryId, final boolean nextPageWanted) {
if (mPaginator.isPagesCompleted())
return;
if (mIsLoadingMore)
return;
mIsLoadingMore = true;
getView().showLoading(true);
Category.fetchSubCategories(categoryId, mPaginator.page + 1, mApiCallback);
}
}
private final SKCallback<ResponseCategories> mApiCallback = new
SKCallback<ResponseCategories>() {
@Override
public void failure(final SKError error) {
mIsLoadingMore = false;
if (isViewAttached())
getView().showError(error);
}
@Override
public void success(ResponseCategories response) {
mIsLoadingMore = false;
mPaginator = response.meta.paginator;
if (response.categories == null) {
if (isViewAttached())
getView().showError(SKError.noResultsError());
return;
}
if (isViewAttached()) {
getView().setData(response.categories);
getView().showContent();
}
}
};
public interface CategoryView<List<Category>> extends MvpView {
void showLoading();
void showContent();
void showError(SKError error);
void setData(List<Category> data);
void loadData();
}
CategoryView interface
CategoriesFragment implementation review
Mosby implementation review
Low level of complexity
Code decoupling, test friendly
Paginator logic and model access moved to
Presenter
Fragment cares only about displaying data
(dumb-stateless)
Memory leaks and random crashes
minimized
Reusable code because our presenter can
be used from other views
No UI flow interruptions, or unneeded
repeated network calls
Mosby implementation issues faced
Issues/limitation faced during
implementation
First versions of Mosby came with a lot of external libraries dependencies
First versions of Mosby were extending from
AppCompatActivity
No available or proposed solution for the pagination pattern, so we have to save and restore
paginator variable from bundle (the only state being kept manually from the presenter)
We did not like that we have to store data to preserve a sane orientation change flow.
ViewState feature can save us from that but could not save us from paginator concept
problems. So, not an overall solution
Using Adapter Delegates in the
Skroutz application
George Metaxas
Skroutz Application facts
Skroutz is a moderate sized application, with a significant number of Android building blocks. The majority of the activities are simply fragment containers. Only a
couple of activities have actual real implementations (e.g. for supporting fragment ViewPagers).
13
Activities
Mostly fragment containers,
although some have actual UI
logic
24
Fragments
Most of the UI logic is
implemented in separate
fragments.
consisting of
15
List Fragments
The majority of the fragments
display some sort of a list of
items
most of which
are
Migrating away from ListView
ListView limitations:
Support section headers
Buggy addHeader/addFooter when
setting adapter after header/footer
Need different components for switching
between lists and grids
Not very easy to handle multiple click
targets per cell
Multiple cell type support is cumbersome
But most important of all...
ListView is as old as
Shakespeare
William Shakespeare
Transition to the RecyclerView component
Requirements
Handling clicks and multiple click targets
Add headers
Add footers
Reuse Adapter code
Keep it DRY and free of spaghetti code
Support showing unrelated entities
Demo app shows how we
tackled the problem
First Iteration – use inheritance
OOP promotes inheritance naturally
Use different View Types (e.g
cell types) per subclass
Base adapter functionality
in generic superclasses
Extend in subclasses
Override getItemViewType for
managing new view types
Add/extend ViewHolders
Give access to existing ViewHolders
– across packages
Our demo app – Adapter Inheritance vs Adapter Delegates
1st
screen - single object type view
Very basic navigation logic, click cell and open same
activity or a different one
Most common use case scenario
Show objects of type Category
Single RecyclerView with simple adapter
CategoriesAdapter implementation
@Override
public int getItemViewType(final int position) {
int baseItemViewType = super.getItemViewType(position);
if (baseItemViewType == INVALID_VIEW) {
return CATEGORY_VIEW_TYPE;
}
return baseItemViewType;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent,
final int viewType) {
if (viewType == CATEGORY_VIEW_TYPE) {
final View newView = mInflater.inflate(R.layout.cell_category, parent, false);
return new CategoriesViewHolder(newView, mClickListener);
}
return super.onCreateViewHolder(parent, viewType);
}
CategoriesAdapter implementation (2)
@Override
public void onBindViewHolder(
final RecyclerView.ViewHolder holder, final int position) {
if (holder.getItemViewType() == CATEGORY_VIEW_TYPE) {
CategoriesViewHolder viewHolder = (CategoriesViewHolder) holder;
Category category = mData.get(position);
viewHolder.categoryText.setText(category.name);
viewHolder.itemView.setTag(category);
} else {
super.onBindViewHolder(holder, position);
}
}
static class CategoriesViewHolder extends RecyclerView.ViewHolder {
TextView categoryText;
CategoriesViewHolder(final View view, final View.OnClickListener onClickListener) {
super(view);
categoryText = (TextView) view.findViewById(R.id.category_text);
view.setOnClickListener(onClickListener);
}
}
Our demo app - Adapter inheritance vs Adapter delegates
2nd screen - complex requirements
Different types of objects (Category vs Shop)
Style the same object type differently, according to
an object’s internal property
Reuse adapter code from the 1st
screen
A more complex example – CategoriesAndShopAdapter
@Override
public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
if (viewType == LEAF_CATEGORY_VIEW_TYPE) {
return new LeafCategoryViewHolder(mInflater.inflate(R.layout.cell_leaf_category,
parent, false), mClickListener);
} else if (viewType == SHOP_VIEW_TYPE) {
return new ShopViewHolder(mInflater.inflate(R.layout.cell_shop,parent, false),
mClickListener);
}
return super.onCreateViewHolder(parent, viewType);
}
@Override
public int getItemViewType(final int position) {
if (position == DEFAULT_SHOP_POSITION) {
return SHOP_VIEW_TYPE;
} else if (mData.get(position).isLeaf) {
return LEAF_CATEGORY_VIEW_TYPE;
}
return super.getItemViewType(position);
}
Our onBindViewHolder just got a bit more complex
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
if (holder.getItemViewType() == LEAF_CATEGORY_VIEW_TYPE) {
LeafCategoryViewHolder viewHolder =(LeafCategoryViewHolder) holder;
viewHolder.categoryText.setText(mData.get(position).name);
if (position % 2 == 0) {
viewHolder.categoryIcon.setImageDrawable(mPlayDrawable);
} else {
viewHolder.categoryIcon.setImageDrawable(mSaveDrawable);
}
} else if (holder.getItemViewType() == SHOP_VIEW_TYPE) {
ShopViewHolder viewHolder = (ShopViewHolder) holder;
viewHolder.shopText.setText(mShop.name);
viewHolder.itemView.setTag(mShop);
} else {
super.onBindViewHolder(holder, position);
}
}
And don’t forget about the View Holders for the additional objects
static class ShopViewHolder extends RecyclerView.ViewHolder {
TextView shopText;
ShopViewHolder(final View view, View.OnClickListener listener) {
super(view);
shopText = (TextView) view.findViewById(R.id.shop_text);
view.setOnClickListener(listener);
}
}
static class LeafCategoryViewHolder extends CategoriesViewHolder {
ImageView categoryIcon;
LeafCategoryViewHolder(final View view, View.OnClickListener listener) {
super(view, listener);
categoryIcon = (ImageView) view.findViewById(R.id.category_icon);
}
}
Scalability issues
Welcome to Adapter Hell!
Very cumbersome to extend - requires adapter modifications
Does not scale very well
Not what inheritance is all about
There has to be something better that we can do
Adapters become maintenance nightmares
Second Iteration - Use Adapter Delegates
A more appropriate solution
https://github.com/sockeqwe/AdapterDelegates
Favours composition over inheritance - delegates pattern
AdapterDelegates are orchestrated through an AdapterDelegatesManager
The decision about which cell will be rendered is given to the AdapterDelegates
Easy to extend
All view rendering logic is moved to the AdapterDelegates
Using AdapterDelegates
Migrating towards Adapter Delegates
Next initialise an instance of the AdapterDelegatesManager in your Base Adapter
In each adapter, create one AdapterDelegate per View Type
Override the base getItemViewType, onCreateViewHolder and onBindViewHolder and
delegate them to the AdapterDelegatesManager
First of all add the gradle dependency
Consider breaking down complex layouts into separate
AdapterDelegates (if possible)
Base Adapter Code
protected final AdapterDelegatesManager<List<T>> mAdapterDelegateManager;
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
mAdapterDelegateManager.onBindViewHolder(mData, position, holder);
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int
viewType) {
return mAdapterDelegateManager.onCreateViewHolder(parent, viewType);
}
@Override
public int getItemViewType(final int position) {
return mAdapterDelegateManager.getItemViewType(mData, position);
}
Modify the concrete adapter implementations - CategoriesAdapter
The CategoriesAdapter becomes empty!
Move the code for the onBindViewHolder, onCreateViewHolder, and the ViewHolder to a
new CategoriesAdapterDelegate class
The new class will implement the AdapterDelegate interface
Control when the adapter will be used by implementing the isForViewType method
Initialise the CategoriesAdapterDelegate in the Adapter and add its instance to the
AdapterDelegatesManager
CategoriesAdapter
public class CategoriesAdapter extends BaseAdapter<Category> {
public CategoriesAdapter(final Context context, final LayoutInflater layoutInflater,
final View.OnClickListener onClickListener, List<Category> data) {
super(context, layoutInflater, onClickListener);
mAdapterDelegateManager.addDelegate(new CategoriesAdapterDelegate(mContext, mInflater,
mClickListener));
}
}
CategoriesAdapterDelegate
public class CategoriesAdapterDelegate implements AdapterDelegate<List<Category>>
@Override
public void onBindViewHolder(@NonNull final List<Category> items, final int position,
@NonNull final RecyclerView.ViewHolder holder) {
CategoriesViewHolder viewHolder = (CategoriesViewHolder) holder;
Category category = items.get(position);
viewHolder.categoryText.setText(category.name);
viewHolder.itemView.setTag(category);
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent) {
return new CategoriesViewHolder(mInflater.inflate(R.layout.cell_category,
parent, false), mClickListener);
}
@Override
public boolean isForViewType(@NonNull final List<Category> items, final int position) {
return true;
}
What about our complex adapter? CategoriesAndShopAdapter
Modifying our complex adapter
Use inheritance and subclass the CategoriesAdapterDelegate for supporting the Leaf
Categories view type
Εxtract the Shop View type to an Adapter Delegate – cannot use it with our
AdapterDelegateManager – not the same generic type
Manage the Shop View Type and delegate everything else to the base class logic
Reuse CategoriesAdapterDelegate as a fallback delegate
CategoriesAndShopAdapter
public CategoriesAndShopAdapter(final Context context, final LayoutInflater layoutInflater,
final View.OnClickListener onClickListener,
final List<Category> data, final Shop shop) {
super(context, layoutInflater, onClickListener);
mAdapterDelegateManager.addDelegate(new LeafCategoriesAdapterDelegate(mContext, mInflater,
mClickListener));
mAdapterDelegateManager.setDefaultDelegate(new CategoriesAdapterDelegate(mContext, mInflater,
mClickListener));
mShopAdapterDelegate = new ShopAdapterDelegate(mContext, mInflater, mClickListener);
}
@Override
public int getItemViewType(final int position) {
if (position == 0) {
return SHOP_VIEW_TYPE;
}
return super.getItemViewType(position);
}
CategoriesAndShopAdapter (2)
@Override
public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
if (viewType == SHOP_VIEW_TYPE) {
return mShopAdapterDelegate.onCreateViewHolder(parent);
}
return mAdapterDelegateManager.onCreateViewHolder(parent, viewType);
}
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
if (holder.getItemViewType() == SHOP_VIEW_TYPE) {
mShopAdapterDelegate.onBindViewHolder(mShop, 0, holder);
} else {
mAdapterDelegateManager.onBindViewHolder(mData, position, holder);
}
}
LeafCategoriesAdapterDelegate
public class LeafCategoriesAdapterDelegate extends CategoriesAdapterDelegate
@Override
public void onBindViewHolder(@NonNull final List<Category> items, final int position, @NonNull final
RecyclerView.ViewHolder holder) {
super.onBindViewHolder(items, position, holder);
LeafCategoryViewHolder viewHolder = (LeafCategoryViewHolder) holder;
if (position % 2 == 0) {
viewHolder.categoryIcon.setImageDrawable(mPlayDrawable);
} else {
viewHolder.categoryIcon.setImageDrawable(mSaveDrawable);
}
}
@Override
public boolean isForViewType(@NonNull final List<Category> items, final int position) {
return items.get(position).isLeaf;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent) {
return new LeafCategoryViewHolder(mInflater.inflate(R.layout.cell_leaf_category,parent, false),
mClickListener);
}
ShopAdapterDelegate
public class ShopAdapterDelegate implements AdapterDelegate<Shop>
@Override
public void onBindViewHolder(@NonNull final Shop items, final int position, @NonNull final
RecyclerView.ViewHolder holder) {
ShopViewHolder viewHolder = (ShopViewHolder) holder;
viewHolder.shopText.setText(items.name);
viewHolder.itemView.setTag(items);
}
@Override
public boolean isForViewType(@NonNull final Shop items, final int position) {
return true;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent) {
return new ShopViewHolder(mInflater.inflate(R.layout.cell_shop,parent, false),
mClickListener);
}
AdapterDelegate - Retrospect
AdapterDelegate benefits:
Reduces the adapter complexity
Increased performance due to:
- Specialised (smaller) layouts
- Avoid excessive branching in onBindViewHolder
Reuse the code across different adapters
Easily extend adapter functionality
Code decoupling
AdapterDelegate – Retrospect (2)
AdapterDelegate limitations:
No easy support for displaying objects of completely different classes
At present, you cannot access AdapterDelegates in the DelegateManager
Skroutz Sample Application https://github.com/skroutz/AdapterDelegatesSample
Resources
Adapter Delegates Library code https://github.com/sockeqwe/AdapterDelegates
Adapter Delegates Original Blog Post http://hannesdorfmann.com/android/adapter-delegates
MVP Library code: https://github.com/sockeqwe/mosby
MVP Original Blog Posts: http://hannesdorfmann.com/mosby/ http://hannesdorfmann.com/mosby/mvp/
Q & A
Thank you
George Metaxas - gmetaxas@skroutz.gr
Chris Mpitzios - cmpi@skroutz.gr

Más contenido relacionado

La actualidad más candente

Angular js 1.3 presentation for fed nov 2014
Angular js 1.3 presentation for fed   nov 2014Angular js 1.3 presentation for fed   nov 2014
Angular js 1.3 presentation for fed nov 2014Sarah Hudson
 
Marionette structure with modules
Marionette structure with modulesMarionette structure with modules
Marionette structure with modulesmatt-briggs
 
Marionette - TorontoJS
Marionette - TorontoJSMarionette - TorontoJS
Marionette - TorontoJSmatt-briggs
 
The SAM Pattern: a Distributed System View of Front-End Architectures
The SAM Pattern: a Distributed System View of Front-End ArchitecturesThe SAM Pattern: a Distributed System View of Front-End Architectures
The SAM Pattern: a Distributed System View of Front-End ArchitecturesJean-Jacques Dubray
 
SUGCon 2014 Sitecore MVC
SUGCon 2014 Sitecore MVCSUGCon 2014 Sitecore MVC
SUGCon 2014 Sitecore MVCmikeedwards83
 
AngularJS introduction
AngularJS introductionAngularJS introduction
AngularJS introductionTania Gonzales
 
From User Action to Framework Reaction
From User Action to Framework ReactionFrom User Action to Framework Reaction
From User Action to Framework Reactionjbandi
 
Gettings started with the superheroic JavaScript library AngularJS
Gettings started with the superheroic JavaScript library AngularJSGettings started with the superheroic JavaScript library AngularJS
Gettings started with the superheroic JavaScript library AngularJSArmin Vieweg
 
Angularjs architecture
Angularjs architectureAngularjs architecture
Angularjs architectureMichael He
 
Make your Backbone Application dance
Make your Backbone Application danceMake your Backbone Application dance
Make your Backbone Application danceNicholas Valbusa
 
Different way to share data between controllers in angular js
Different way to share data between controllers in angular jsDifferent way to share data between controllers in angular js
Different way to share data between controllers in angular jscodeandyou forums
 

La actualidad más candente (20)

Angular js 1.3 presentation for fed nov 2014
Angular js 1.3 presentation for fed   nov 2014Angular js 1.3 presentation for fed   nov 2014
Angular js 1.3 presentation for fed nov 2014
 
Marionette structure with modules
Marionette structure with modulesMarionette structure with modules
Marionette structure with modules
 
Angularjs Basics
Angularjs BasicsAngularjs Basics
Angularjs Basics
 
AngularJS Basics
AngularJS BasicsAngularJS Basics
AngularJS Basics
 
Introduction to Unit Tests and TDD
Introduction to Unit Tests and TDDIntroduction to Unit Tests and TDD
Introduction to Unit Tests and TDD
 
Jinal desai .net
Jinal desai .netJinal desai .net
Jinal desai .net
 
Marionette - TorontoJS
Marionette - TorontoJSMarionette - TorontoJS
Marionette - TorontoJS
 
The SAM Pattern: a Distributed System View of Front-End Architectures
The SAM Pattern: a Distributed System View of Front-End ArchitecturesThe SAM Pattern: a Distributed System View of Front-End Architectures
The SAM Pattern: a Distributed System View of Front-End Architectures
 
SUGCon 2014 Sitecore MVC
SUGCon 2014 Sitecore MVCSUGCon 2014 Sitecore MVC
SUGCon 2014 Sitecore MVC
 
AngularJS introduction
AngularJS introductionAngularJS introduction
AngularJS introduction
 
AngularJS Best Practices
AngularJS Best PracticesAngularJS Best Practices
AngularJS Best Practices
 
From User Action to Framework Reaction
From User Action to Framework ReactionFrom User Action to Framework Reaction
From User Action to Framework Reaction
 
Angular from Scratch
Angular from ScratchAngular from Scratch
Angular from Scratch
 
AngularJS Best Practices
AngularJS Best PracticesAngularJS Best Practices
AngularJS Best Practices
 
5 angularjs features
5 angularjs features5 angularjs features
5 angularjs features
 
React vs-angular-mobile
React vs-angular-mobileReact vs-angular-mobile
React vs-angular-mobile
 
Gettings started with the superheroic JavaScript library AngularJS
Gettings started with the superheroic JavaScript library AngularJSGettings started with the superheroic JavaScript library AngularJS
Gettings started with the superheroic JavaScript library AngularJS
 
Angularjs architecture
Angularjs architectureAngularjs architecture
Angularjs architecture
 
Make your Backbone Application dance
Make your Backbone Application danceMake your Backbone Application dance
Make your Backbone Application dance
 
Different way to share data between controllers in angular js
Different way to share data between controllers in angular jsDifferent way to share data between controllers in angular js
Different way to share data between controllers in angular js
 

Destacado

Android Effective UI: Tips, Tricks and Patterns
Android Effective UI: Tips, Tricks and PatternsAndroid Effective UI: Tips, Tricks and Patterns
Android Effective UI: Tips, Tricks and PatternsAdham Enaya
 
Android Architecture MVP Pattern
Android Architecture MVP Pattern Android Architecture MVP Pattern
Android Architecture MVP Pattern Jeff Potter
 
Clean Architecture
Clean ArchitectureClean Architecture
Clean ArchitectureBadoo
 
Clean architecture
Clean architectureClean architecture
Clean architectureandbed
 
Mvp in practice
Mvp in practiceMvp in practice
Mvp in practice彥彬 洪
 
Design pattern in android
Design pattern in androidDesign pattern in android
Design pattern in androidJay Kumarr
 
Aggregate db Week 6 Hacking for Diplomacy
Aggregate db Week 6 Hacking for DiplomacyAggregate db Week 6 Hacking for Diplomacy
Aggregate db Week 6 Hacking for DiplomacyStanford University
 
Hacking CT Hacking for Diplomacy week 8
Hacking CT Hacking for Diplomacy week 8Hacking CT Hacking for Diplomacy week 8
Hacking CT Hacking for Diplomacy week 8Stanford University
 
Team 621Week 6 Hacking for Diplomacy
Team 621Week 6 Hacking for DiplomacyTeam 621Week 6 Hacking for Diplomacy
Team 621Week 6 Hacking for DiplomacyStanford University
 
Space Evaders Hacking for Diplomacy week 8
Space Evaders Hacking for Diplomacy week 8Space Evaders Hacking for Diplomacy week 8
Space Evaders Hacking for Diplomacy week 8Stanford University
 
Peacekeeping Week 6 Hacking for Diplomacy
Peacekeeping Week 6 Hacking for DiplomacyPeacekeeping Week 6 Hacking for Diplomacy
Peacekeeping Week 6 Hacking for DiplomacyStanford University
 
Team 621 Hacking for Diplomacy week 8
Team 621 Hacking for Diplomacy week 8Team 621 Hacking for Diplomacy week 8
Team 621 Hacking for Diplomacy week 8Stanford University
 
architecture of mobile software applications
architecture of mobile software applicationsarchitecture of mobile software applications
architecture of mobile software applicationsHassan Dar
 
Trace Lessons Learned H4Dip Stanford 2016
Trace Lessons Learned H4Dip Stanford 2016 Trace Lessons Learned H4Dip Stanford 2016
Trace Lessons Learned H4Dip Stanford 2016 Stanford University
 
Aggregate db Lessons Learned H4Dip Stanford 2016
Aggregate db Lessons Learned H4Dip Stanford 2016Aggregate db Lessons Learned H4Dip Stanford 2016
Aggregate db Lessons Learned H4Dip Stanford 2016Stanford University
 
Peacekeeping Lessons Learned H4Dip Stanford 2016
Peacekeeping Lessons Learned H4Dip Stanford 2016Peacekeeping Lessons Learned H4Dip Stanford 2016
Peacekeeping Lessons Learned H4Dip Stanford 2016Stanford University
 
Space Evaders Lessons Learned H4Dip Stanford 2016
Space Evaders Lessons Learned H4Dip Stanford 2016Space Evaders Lessons Learned H4Dip Stanford 2016
Space Evaders Lessons Learned H4Dip Stanford 2016Stanford University
 
Exodus Lessons Learned H4Dip Stanford 2016
Exodus Lessons Learned H4Dip Stanford 2016Exodus Lessons Learned H4Dip Stanford 2016
Exodus Lessons Learned H4Dip Stanford 2016Stanford University
 

Destacado (20)

Android Effective UI: Tips, Tricks and Patterns
Android Effective UI: Tips, Tricks and PatternsAndroid Effective UI: Tips, Tricks and Patterns
Android Effective UI: Tips, Tricks and Patterns
 
Android Architecture MVP Pattern
Android Architecture MVP Pattern Android Architecture MVP Pattern
Android Architecture MVP Pattern
 
Clean Architecture
Clean ArchitectureClean Architecture
Clean Architecture
 
Clean architecture
Clean architectureClean architecture
Clean architecture
 
MVP 패턴 소개
MVP 패턴 소개MVP 패턴 소개
MVP 패턴 소개
 
Mvp in practice
Mvp in practiceMvp in practice
Mvp in practice
 
Design pattern in android
Design pattern in androidDesign pattern in android
Design pattern in android
 
Aggregate db Week 6 Hacking for Diplomacy
Aggregate db Week 6 Hacking for DiplomacyAggregate db Week 6 Hacking for Diplomacy
Aggregate db Week 6 Hacking for Diplomacy
 
Hacking CT Hacking for Diplomacy week 8
Hacking CT Hacking for Diplomacy week 8Hacking CT Hacking for Diplomacy week 8
Hacking CT Hacking for Diplomacy week 8
 
Team 621Week 6 Hacking for Diplomacy
Team 621Week 6 Hacking for DiplomacyTeam 621Week 6 Hacking for Diplomacy
Team 621Week 6 Hacking for Diplomacy
 
Space Evaders Hacking for Diplomacy week 8
Space Evaders Hacking for Diplomacy week 8Space Evaders Hacking for Diplomacy week 8
Space Evaders Hacking for Diplomacy week 8
 
Peacekeeping Week 6 Hacking for Diplomacy
Peacekeeping Week 6 Hacking for DiplomacyPeacekeeping Week 6 Hacking for Diplomacy
Peacekeeping Week 6 Hacking for Diplomacy
 
Team 621 Hacking for Diplomacy week 8
Team 621 Hacking for Diplomacy week 8Team 621 Hacking for Diplomacy week 8
Team 621 Hacking for Diplomacy week 8
 
Relentlessly Direct
Relentlessly DirectRelentlessly Direct
Relentlessly Direct
 
architecture of mobile software applications
architecture of mobile software applicationsarchitecture of mobile software applications
architecture of mobile software applications
 
Trace Lessons Learned H4Dip Stanford 2016
Trace Lessons Learned H4Dip Stanford 2016 Trace Lessons Learned H4Dip Stanford 2016
Trace Lessons Learned H4Dip Stanford 2016
 
Aggregate db Lessons Learned H4Dip Stanford 2016
Aggregate db Lessons Learned H4Dip Stanford 2016Aggregate db Lessons Learned H4Dip Stanford 2016
Aggregate db Lessons Learned H4Dip Stanford 2016
 
Peacekeeping Lessons Learned H4Dip Stanford 2016
Peacekeeping Lessons Learned H4Dip Stanford 2016Peacekeeping Lessons Learned H4Dip Stanford 2016
Peacekeeping Lessons Learned H4Dip Stanford 2016
 
Space Evaders Lessons Learned H4Dip Stanford 2016
Space Evaders Lessons Learned H4Dip Stanford 2016Space Evaders Lessons Learned H4Dip Stanford 2016
Space Evaders Lessons Learned H4Dip Stanford 2016
 
Exodus Lessons Learned H4Dip Stanford 2016
Exodus Lessons Learned H4Dip Stanford 2016Exodus Lessons Learned H4Dip Stanford 2016
Exodus Lessons Learned H4Dip Stanford 2016
 

Similar a Skroutz Android MVP and Adapter Delegates presentation

Clean Architecture on Android
Clean Architecture on AndroidClean Architecture on Android
Clean Architecture on AndroidTianming Xu
 
Say bye to Fragments with Conductor & Kotlin
Say bye to Fragments with Conductor & KotlinSay bye to Fragments with Conductor & Kotlin
Say bye to Fragments with Conductor & KotlinMiquel Beltran Febrer
 
My perspective on MVP and architecture discussions
My perspective on MVP and architecture discussionsMy perspective on MVP and architecture discussions
My perspective on MVP and architecture discussionsPaul Blundell
 
JSAnkara Swift v React Native
JSAnkara Swift v React NativeJSAnkara Swift v React Native
JSAnkara Swift v React NativeMuhammed Demirci
 
ASP.NET MVC Internals
ASP.NET MVC InternalsASP.NET MVC Internals
ASP.NET MVC InternalsVitaly Baum
 
Modern Android app library stack
Modern Android app library stackModern Android app library stack
Modern Android app library stackTomáš Kypta
 
Multi Client Development with Spring
Multi Client Development with SpringMulti Client Development with Spring
Multi Client Development with SpringJoshua Long
 
Dropwizard Introduction
Dropwizard IntroductionDropwizard Introduction
Dropwizard IntroductionAnthony Chen
 
Building Scalable Stateless Applications with RxJava
Building Scalable Stateless Applications with RxJavaBuilding Scalable Stateless Applications with RxJava
Building Scalable Stateless Applications with RxJavaRick Warren
 
Overview of Android Infrastructure
Overview of Android InfrastructureOverview of Android Infrastructure
Overview of Android InfrastructureAlexey Buzdin
 
Overview of Android Infrastructure
Overview of Android InfrastructureOverview of Android Infrastructure
Overview of Android InfrastructureC.T.Co
 
iOS Architectures
iOS ArchitecturesiOS Architectures
iOS ArchitecturesHung Hoang
 
Mvc interview questions – deep dive jinal desai
Mvc interview questions – deep dive   jinal desaiMvc interview questions – deep dive   jinal desai
Mvc interview questions – deep dive jinal desaijinaldesailive
 
Making React Native UI Components with Swift
Making React Native UI Components with SwiftMaking React Native UI Components with Swift
Making React Native UI Components with SwiftRay Deck
 
L2 Web App Development Guest Lecture At University of Surrey 20/11/09
L2 Web App Development Guest Lecture At University of Surrey 20/11/09L2 Web App Development Guest Lecture At University of Surrey 20/11/09
L2 Web App Development Guest Lecture At University of Surrey 20/11/09Daniel Bryant
 
Patterns Are Good For Managers
Patterns Are Good For ManagersPatterns Are Good For Managers
Patterns Are Good For ManagersAgileThought
 
From User Action to Framework Reaction
From User Action to Framework ReactionFrom User Action to Framework Reaction
From User Action to Framework Reactionjbandi
 

Similar a Skroutz Android MVP and Adapter Delegates presentation (20)

Spring Web MVC
Spring Web MVCSpring Web MVC
Spring Web MVC
 
Clean Architecture on Android
Clean Architecture on AndroidClean Architecture on Android
Clean Architecture on Android
 
ASP .net MVC
ASP .net MVCASP .net MVC
ASP .net MVC
 
Android development
Android developmentAndroid development
Android development
 
Say bye to Fragments with Conductor & Kotlin
Say bye to Fragments with Conductor & KotlinSay bye to Fragments with Conductor & Kotlin
Say bye to Fragments with Conductor & Kotlin
 
My perspective on MVP and architecture discussions
My perspective on MVP and architecture discussionsMy perspective on MVP and architecture discussions
My perspective on MVP and architecture discussions
 
JSAnkara Swift v React Native
JSAnkara Swift v React NativeJSAnkara Swift v React Native
JSAnkara Swift v React Native
 
ASP.NET MVC Internals
ASP.NET MVC InternalsASP.NET MVC Internals
ASP.NET MVC Internals
 
Modern Android app library stack
Modern Android app library stackModern Android app library stack
Modern Android app library stack
 
Multi Client Development with Spring
Multi Client Development with SpringMulti Client Development with Spring
Multi Client Development with Spring
 
Dropwizard Introduction
Dropwizard IntroductionDropwizard Introduction
Dropwizard Introduction
 
Building Scalable Stateless Applications with RxJava
Building Scalable Stateless Applications with RxJavaBuilding Scalable Stateless Applications with RxJava
Building Scalable Stateless Applications with RxJava
 
Overview of Android Infrastructure
Overview of Android InfrastructureOverview of Android Infrastructure
Overview of Android Infrastructure
 
Overview of Android Infrastructure
Overview of Android InfrastructureOverview of Android Infrastructure
Overview of Android Infrastructure
 
iOS Architectures
iOS ArchitecturesiOS Architectures
iOS Architectures
 
Mvc interview questions – deep dive jinal desai
Mvc interview questions – deep dive   jinal desaiMvc interview questions – deep dive   jinal desai
Mvc interview questions – deep dive jinal desai
 
Making React Native UI Components with Swift
Making React Native UI Components with SwiftMaking React Native UI Components with Swift
Making React Native UI Components with Swift
 
L2 Web App Development Guest Lecture At University of Surrey 20/11/09
L2 Web App Development Guest Lecture At University of Surrey 20/11/09L2 Web App Development Guest Lecture At University of Surrey 20/11/09
L2 Web App Development Guest Lecture At University of Surrey 20/11/09
 
Patterns Are Good For Managers
Patterns Are Good For ManagersPatterns Are Good For Managers
Patterns Are Good For Managers
 
From User Action to Framework Reaction
From User Action to Framework ReactionFrom User Action to Framework Reaction
From User Action to Framework Reaction
 

Skroutz Android MVP and Adapter Delegates presentation

  • 1. Using MVP in the Skroutz application Chris Mpitzios
  • 2. MVC approach and Skroutz Application facts Skroutz is a network intensive application. Almost all of its screens require communication with Skroutz API in order to fetch and display relevant data. ● 11% responsible for static content displaying. ● 89% responsible for network communication ● Only portrait orientation supported ● Average LOC for active presentation elements ≈500 lines (excluding placeholder activities) ● All network related screens follow the same view state pattern: ○ Load in the background ○ Display loading view ○ Display fetched data or an error message if needed 27 Screens
  • 3. Skroutz App with MVC Expectations from MVC approach: Good separation of concerns Clean, reusable, testable code Minimizing of spaghetti effect Keep code complexity low
  • 4. MVC does not fit Android
  • 5. Skroutz App with MVC Review of MVC approach: View just structures displayed interface High complexity - Spaghetti code - hard to test code- Unmaintainable state RESULT One god object responsible for everything MVC-View responsibilities move to Controller Controller should: data bind, manage animations, user interactions ……..
  • 6. Additional requirements Eliminate memory leaks Support orientation changes Define abstractions for repeated view flows (Load-Content-Error)
  • 7. Separates presentation layer from the logic Presenter: ● fetches, formats, delivers data to View ● instruct View for UI actions according to data ● keeps a reference to both View and Model View : ● completely passive ● displays data ● cannot access model MVP to the rescue Controller is part of the view View-Presenter -> one-to-one relationship Multiple Presenters for complex Views Next alternative
  • 8. Let's implement MVP How? Custom implementation or use an existing library? Check available implementations before reinventing the wheel Most popular implementations at the time were Mosby and Nucleus
  • 10. Mosby vs Nucleus public interface MainActivityView extends MvpView { void showLoading(); void setData(API.Item[] response); void showContent(); void showError(Throwable throwable); } public class MainPresenter extends MvpBasePresenter<MainActivityView> { // Public public void loadData() { return App.getRestAdapterInstance().getItems(mApiCallback); } private final Callback<API.Item[]> mApiCallback = new Callback<API.Item[]>() { @Override public void failure(final Throwable throwable) { if (isViewAttached()) getView().showError(throwable); } @Override public void success(API.Item[] response) { if (isViewAttached()) getView().setData(response); } }; } Mosby Presenter implementation: MainActivityView interface: public class MainPresenter extends RxPresenter<MainActivity> { private static final int REQUEST_ITEMS_RESTARTABLE_ID = 1; @Override public void onCreate(Bundle savedState) { super.onCreate(savedState); restartableLatestCache(REQUEST_ITEMS_RESTARTABLE_ID, new Func0<Observable<ServerAPI.Response>>() { @Override public Observable<ServerAPI.Response> call() { return App.getServerAPI() .getItems(“request Argument”) .observeOn(AndroidSchedulers.mainThread()); } }, new Action2<MainActivity, ServerAPI.Response>() { @Override public void call(MainActivity activity, ServerAPI.Response response) { activity.setData(response.items, “requestArgument”); } }, new Action2<MainActivity, Throwable>() { @Override public void call(MainActivity activity, Throwable throwable) { activity.showError(throwable); } }); if (savedState == null) start(REQUEST_ITEMS_RESTARTABLE_ID); } // Public public void loadData() { start(REQUEST_ITEMS_RESTARTABLE_ID); } } Nucleus Presenter implementation:
  • 11. Mosby vs Nucleus An important difference Nucleus presenter had an onCreate() method which means that some kind of lifecycle exists Mosby on the other hand does not retain Presenters in any way
  • 12. Mosby vs Nucleus Main concepts and differences Custom annotations to inject presenters Nucleus:Mosby: RxJava/observables used and mandatory No separate interface for view actions Presenters have a primitive lifecycle All running tasks reattached automatically Presenters can survive an activity recreation Based on delegation so one can use delegates to integrate it in a custom way Presenters must be explicitly provided MvpView as API for view related methods Presenters independent from view’s lifecycle Previous running tasks not restored Presenters are not retained in any way Based on delegation so one can use delegates to integrate it in a custom way ViewState provided to restore UI state RxJava/observables not mandatory Mosby was more documented and embraced from the community
  • 13. Skroutz App Categories screen Main concepts One placeholder activity hosting Categories fragment Network call to API’s Categories endpoint Endpoint requires/provides paging Retrofit (and OkHttp) used Both orientations supported One RecyclerView to display data
  • 14. CategoriesFragment MVC implementation public class CategoriesFragment extends Fragment implements AdapterView.OnItemClickListener { private AbstractRecyclerViewAdapter<Category> mAdapter; private Paginator mPaginator = new Paginator(); private boolean mIsAlreadyLoading= false; @Override public void onActivityCreated(@Nullable final Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mCategory = getActivity().getIntent().getParcelableExtra(KEY_BUNDLE_CATEGORY); loadData(); } @Override public void onDestroy() { super.onDestroy(); mApiCallback.invalidate(); } private void setData(List<Category> list) { if (list == null){ showError(SKError.noResultsError()); return; } mAdapter.addData(list); mAdapter.notifyDataSetChanged(); showError(null); } private void showLoading() {............} private void showContent() {............} private void showError(SKError error) {............} private void loadData() { if (mPaginator.isPagesCompleted()) return; if (mIsAlreadyLoading) return; mIsAlreadyLoading = true; showLoading(); Category.fetchSubCategories(mCategory.id, mCurrentPaginator.page + 1, mApiCallback); } private final SKCallback<ResponseCategories> mApiCallback = new SKCallback<ResponseCategories>() { @Override public void failure(final SKError error) { mIsAlreadyLoading = false; showError(error); } @Override public void success(ResponseCategories response) { mIsAlreadyLoading = false; mPaginator = response.meta.paginator; setData(response.categories); showContent() } };
  • 15. CategoriesFragment MVC implementation review Unneeded complexity, no separation of concerns! Everything takes place in Fragment (View). Network calls, interface handling.. Paginator (part of business logic) included and preserved in View Memory leaks and random crashes detected UI flow for orientation change was broken View is not dumb but absolutely stateful
  • 17. CategoriesFragment Mosby implementation public class CategoriesFragment extends MvpFragment<CategoriesView, CategoriesPresenter, Category> implements CategoriesView{ private AbstractRecyclerViewAdapter<Category> mAdapter; @Override public void onActivityCreated(@Nullable final Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mCategory = getActivity().getIntent().getParcelableExtra(KEY_BUNDLE_CATEGORY); if (savedInstanceState != null) { presenter.onRestoreInstanceState(savedInstanceState); if (getSavedDataFromBundle() != null) { setData(getSavedDataFromBundle()); return; } } loadData(); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); presenter.onSaveInstanceState(outState); outState.putParcelableArrayList(KEY_BUNDLE_DATA, (ArrayList<Category>) mAdapter.getData()); } @Override public CategoriesPresenter createPresenter() { return new CategoriesPresenter(); } @Override public void loadData() { presenter.loadData(mCategory.id); } @Override public void setData(List<Category> list) { mAdapter.addData(list); mAdapter.notifyDataSetChanged(); showError(null); } @Override public void showLoading() {............} @Override public void showContent() {............} @Override public void showError(SKError error) {............}
  • 18. CategoryPresenter and CategoryView - Mosby public class CategoryPresenter extends MvpBasePresenter<CategoryView> { // Attributes private boolean mIsLoadingMore = false; private Paginator mPaginator = new Paginator(); public void onRestoreInstanceState(Bundle savedInstanceState) { mPaginator = savedInstanceState.getParcelable(KEY_BUNDLE_PAGINATOR); } public void onSaveInstanceState(Bundle savedInstanceState) { savedInstanceState.putParcelable(KEY_BUNDLE_PAGINATOR, mPaginator); } public void loadData(final int categoryId, final boolean nextPageWanted) { if (mPaginator.isPagesCompleted()) return; if (mIsLoadingMore) return; mIsLoadingMore = true; getView().showLoading(true); Category.fetchSubCategories(categoryId, mPaginator.page + 1, mApiCallback); } } private final SKCallback<ResponseCategories> mApiCallback = new SKCallback<ResponseCategories>() { @Override public void failure(final SKError error) { mIsLoadingMore = false; if (isViewAttached()) getView().showError(error); } @Override public void success(ResponseCategories response) { mIsLoadingMore = false; mPaginator = response.meta.paginator; if (response.categories == null) { if (isViewAttached()) getView().showError(SKError.noResultsError()); return; } if (isViewAttached()) { getView().setData(response.categories); getView().showContent(); } } }; public interface CategoryView<List<Category>> extends MvpView { void showLoading(); void showContent(); void showError(SKError error); void setData(List<Category> data); void loadData(); } CategoryView interface
  • 19. CategoriesFragment implementation review Mosby implementation review Low level of complexity Code decoupling, test friendly Paginator logic and model access moved to Presenter Fragment cares only about displaying data (dumb-stateless) Memory leaks and random crashes minimized Reusable code because our presenter can be used from other views No UI flow interruptions, or unneeded repeated network calls
  • 20. Mosby implementation issues faced Issues/limitation faced during implementation First versions of Mosby came with a lot of external libraries dependencies First versions of Mosby were extending from AppCompatActivity No available or proposed solution for the pagination pattern, so we have to save and restore paginator variable from bundle (the only state being kept manually from the presenter) We did not like that we have to store data to preserve a sane orientation change flow. ViewState feature can save us from that but could not save us from paginator concept problems. So, not an overall solution
  • 21. Using Adapter Delegates in the Skroutz application George Metaxas
  • 22. Skroutz Application facts Skroutz is a moderate sized application, with a significant number of Android building blocks. The majority of the activities are simply fragment containers. Only a couple of activities have actual real implementations (e.g. for supporting fragment ViewPagers). 13 Activities Mostly fragment containers, although some have actual UI logic 24 Fragments Most of the UI logic is implemented in separate fragments. consisting of 15 List Fragments The majority of the fragments display some sort of a list of items most of which are
  • 23. Migrating away from ListView ListView limitations: Support section headers Buggy addHeader/addFooter when setting adapter after header/footer Need different components for switching between lists and grids Not very easy to handle multiple click targets per cell Multiple cell type support is cumbersome But most important of all...
  • 24. ListView is as old as Shakespeare William Shakespeare
  • 25. Transition to the RecyclerView component Requirements Handling clicks and multiple click targets Add headers Add footers Reuse Adapter code Keep it DRY and free of spaghetti code Support showing unrelated entities Demo app shows how we tackled the problem
  • 26. First Iteration – use inheritance OOP promotes inheritance naturally Use different View Types (e.g cell types) per subclass Base adapter functionality in generic superclasses Extend in subclasses Override getItemViewType for managing new view types Add/extend ViewHolders Give access to existing ViewHolders – across packages
  • 27. Our demo app – Adapter Inheritance vs Adapter Delegates 1st screen - single object type view Very basic navigation logic, click cell and open same activity or a different one Most common use case scenario Show objects of type Category Single RecyclerView with simple adapter
  • 28. CategoriesAdapter implementation @Override public int getItemViewType(final int position) { int baseItemViewType = super.getItemViewType(position); if (baseItemViewType == INVALID_VIEW) { return CATEGORY_VIEW_TYPE; } return baseItemViewType; } @Override public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) { if (viewType == CATEGORY_VIEW_TYPE) { final View newView = mInflater.inflate(R.layout.cell_category, parent, false); return new CategoriesViewHolder(newView, mClickListener); } return super.onCreateViewHolder(parent, viewType); }
  • 29. CategoriesAdapter implementation (2) @Override public void onBindViewHolder( final RecyclerView.ViewHolder holder, final int position) { if (holder.getItemViewType() == CATEGORY_VIEW_TYPE) { CategoriesViewHolder viewHolder = (CategoriesViewHolder) holder; Category category = mData.get(position); viewHolder.categoryText.setText(category.name); viewHolder.itemView.setTag(category); } else { super.onBindViewHolder(holder, position); } } static class CategoriesViewHolder extends RecyclerView.ViewHolder { TextView categoryText; CategoriesViewHolder(final View view, final View.OnClickListener onClickListener) { super(view); categoryText = (TextView) view.findViewById(R.id.category_text); view.setOnClickListener(onClickListener); } }
  • 30. Our demo app - Adapter inheritance vs Adapter delegates 2nd screen - complex requirements Different types of objects (Category vs Shop) Style the same object type differently, according to an object’s internal property Reuse adapter code from the 1st screen
  • 31. A more complex example – CategoriesAndShopAdapter @Override public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) { if (viewType == LEAF_CATEGORY_VIEW_TYPE) { return new LeafCategoryViewHolder(mInflater.inflate(R.layout.cell_leaf_category, parent, false), mClickListener); } else if (viewType == SHOP_VIEW_TYPE) { return new ShopViewHolder(mInflater.inflate(R.layout.cell_shop,parent, false), mClickListener); } return super.onCreateViewHolder(parent, viewType); } @Override public int getItemViewType(final int position) { if (position == DEFAULT_SHOP_POSITION) { return SHOP_VIEW_TYPE; } else if (mData.get(position).isLeaf) { return LEAF_CATEGORY_VIEW_TYPE; } return super.getItemViewType(position); }
  • 32. Our onBindViewHolder just got a bit more complex @Override public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) { if (holder.getItemViewType() == LEAF_CATEGORY_VIEW_TYPE) { LeafCategoryViewHolder viewHolder =(LeafCategoryViewHolder) holder; viewHolder.categoryText.setText(mData.get(position).name); if (position % 2 == 0) { viewHolder.categoryIcon.setImageDrawable(mPlayDrawable); } else { viewHolder.categoryIcon.setImageDrawable(mSaveDrawable); } } else if (holder.getItemViewType() == SHOP_VIEW_TYPE) { ShopViewHolder viewHolder = (ShopViewHolder) holder; viewHolder.shopText.setText(mShop.name); viewHolder.itemView.setTag(mShop); } else { super.onBindViewHolder(holder, position); } }
  • 33. And don’t forget about the View Holders for the additional objects static class ShopViewHolder extends RecyclerView.ViewHolder { TextView shopText; ShopViewHolder(final View view, View.OnClickListener listener) { super(view); shopText = (TextView) view.findViewById(R.id.shop_text); view.setOnClickListener(listener); } } static class LeafCategoryViewHolder extends CategoriesViewHolder { ImageView categoryIcon; LeafCategoryViewHolder(final View view, View.OnClickListener listener) { super(view, listener); categoryIcon = (ImageView) view.findViewById(R.id.category_icon); } }
  • 34. Scalability issues Welcome to Adapter Hell! Very cumbersome to extend - requires adapter modifications Does not scale very well Not what inheritance is all about There has to be something better that we can do Adapters become maintenance nightmares
  • 35. Second Iteration - Use Adapter Delegates A more appropriate solution https://github.com/sockeqwe/AdapterDelegates Favours composition over inheritance - delegates pattern AdapterDelegates are orchestrated through an AdapterDelegatesManager The decision about which cell will be rendered is given to the AdapterDelegates Easy to extend All view rendering logic is moved to the AdapterDelegates
  • 36. Using AdapterDelegates Migrating towards Adapter Delegates Next initialise an instance of the AdapterDelegatesManager in your Base Adapter In each adapter, create one AdapterDelegate per View Type Override the base getItemViewType, onCreateViewHolder and onBindViewHolder and delegate them to the AdapterDelegatesManager First of all add the gradle dependency Consider breaking down complex layouts into separate AdapterDelegates (if possible)
  • 37. Base Adapter Code protected final AdapterDelegatesManager<List<T>> mAdapterDelegateManager; @Override public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) { mAdapterDelegateManager.onBindViewHolder(mData, position, holder); } @Override public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) { return mAdapterDelegateManager.onCreateViewHolder(parent, viewType); } @Override public int getItemViewType(final int position) { return mAdapterDelegateManager.getItemViewType(mData, position); }
  • 38. Modify the concrete adapter implementations - CategoriesAdapter The CategoriesAdapter becomes empty! Move the code for the onBindViewHolder, onCreateViewHolder, and the ViewHolder to a new CategoriesAdapterDelegate class The new class will implement the AdapterDelegate interface Control when the adapter will be used by implementing the isForViewType method Initialise the CategoriesAdapterDelegate in the Adapter and add its instance to the AdapterDelegatesManager
  • 39. CategoriesAdapter public class CategoriesAdapter extends BaseAdapter<Category> { public CategoriesAdapter(final Context context, final LayoutInflater layoutInflater, final View.OnClickListener onClickListener, List<Category> data) { super(context, layoutInflater, onClickListener); mAdapterDelegateManager.addDelegate(new CategoriesAdapterDelegate(mContext, mInflater, mClickListener)); } }
  • 40. CategoriesAdapterDelegate public class CategoriesAdapterDelegate implements AdapterDelegate<List<Category>> @Override public void onBindViewHolder(@NonNull final List<Category> items, final int position, @NonNull final RecyclerView.ViewHolder holder) { CategoriesViewHolder viewHolder = (CategoriesViewHolder) holder; Category category = items.get(position); viewHolder.categoryText.setText(category.name); viewHolder.itemView.setTag(category); } @NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent) { return new CategoriesViewHolder(mInflater.inflate(R.layout.cell_category, parent, false), mClickListener); } @Override public boolean isForViewType(@NonNull final List<Category> items, final int position) { return true; }
  • 41. What about our complex adapter? CategoriesAndShopAdapter Modifying our complex adapter Use inheritance and subclass the CategoriesAdapterDelegate for supporting the Leaf Categories view type Εxtract the Shop View type to an Adapter Delegate – cannot use it with our AdapterDelegateManager – not the same generic type Manage the Shop View Type and delegate everything else to the base class logic Reuse CategoriesAdapterDelegate as a fallback delegate
  • 42. CategoriesAndShopAdapter public CategoriesAndShopAdapter(final Context context, final LayoutInflater layoutInflater, final View.OnClickListener onClickListener, final List<Category> data, final Shop shop) { super(context, layoutInflater, onClickListener); mAdapterDelegateManager.addDelegate(new LeafCategoriesAdapterDelegate(mContext, mInflater, mClickListener)); mAdapterDelegateManager.setDefaultDelegate(new CategoriesAdapterDelegate(mContext, mInflater, mClickListener)); mShopAdapterDelegate = new ShopAdapterDelegate(mContext, mInflater, mClickListener); } @Override public int getItemViewType(final int position) { if (position == 0) { return SHOP_VIEW_TYPE; } return super.getItemViewType(position); }
  • 43. CategoriesAndShopAdapter (2) @Override public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) { if (viewType == SHOP_VIEW_TYPE) { return mShopAdapterDelegate.onCreateViewHolder(parent); } return mAdapterDelegateManager.onCreateViewHolder(parent, viewType); } @Override public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) { if (holder.getItemViewType() == SHOP_VIEW_TYPE) { mShopAdapterDelegate.onBindViewHolder(mShop, 0, holder); } else { mAdapterDelegateManager.onBindViewHolder(mData, position, holder); } }
  • 44. LeafCategoriesAdapterDelegate public class LeafCategoriesAdapterDelegate extends CategoriesAdapterDelegate @Override public void onBindViewHolder(@NonNull final List<Category> items, final int position, @NonNull final RecyclerView.ViewHolder holder) { super.onBindViewHolder(items, position, holder); LeafCategoryViewHolder viewHolder = (LeafCategoryViewHolder) holder; if (position % 2 == 0) { viewHolder.categoryIcon.setImageDrawable(mPlayDrawable); } else { viewHolder.categoryIcon.setImageDrawable(mSaveDrawable); } } @Override public boolean isForViewType(@NonNull final List<Category> items, final int position) { return items.get(position).isLeaf; } @NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent) { return new LeafCategoryViewHolder(mInflater.inflate(R.layout.cell_leaf_category,parent, false), mClickListener); }
  • 45. ShopAdapterDelegate public class ShopAdapterDelegate implements AdapterDelegate<Shop> @Override public void onBindViewHolder(@NonNull final Shop items, final int position, @NonNull final RecyclerView.ViewHolder holder) { ShopViewHolder viewHolder = (ShopViewHolder) holder; viewHolder.shopText.setText(items.name); viewHolder.itemView.setTag(items); } @Override public boolean isForViewType(@NonNull final Shop items, final int position) { return true; } @NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent) { return new ShopViewHolder(mInflater.inflate(R.layout.cell_shop,parent, false), mClickListener); }
  • 46. AdapterDelegate - Retrospect AdapterDelegate benefits: Reduces the adapter complexity Increased performance due to: - Specialised (smaller) layouts - Avoid excessive branching in onBindViewHolder Reuse the code across different adapters Easily extend adapter functionality Code decoupling
  • 47. AdapterDelegate – Retrospect (2) AdapterDelegate limitations: No easy support for displaying objects of completely different classes At present, you cannot access AdapterDelegates in the DelegateManager
  • 48. Skroutz Sample Application https://github.com/skroutz/AdapterDelegatesSample Resources Adapter Delegates Library code https://github.com/sockeqwe/AdapterDelegates Adapter Delegates Original Blog Post http://hannesdorfmann.com/android/adapter-delegates MVP Library code: https://github.com/sockeqwe/mosby MVP Original Blog Posts: http://hannesdorfmann.com/mosby/ http://hannesdorfmann.com/mosby/mvp/
  • 49. Q & A Thank you George Metaxas - gmetaxas@skroutz.gr Chris Mpitzios - cmpi@skroutz.gr