Slide 1

Slide 1 text

ALFONZ Petr Nohejl

Slide 2

Slide 2 text

ALFONZ ● Utilities and helper classes ● Multi purpose library ● Easier development & avoid boilerplate code ● In 2012: github.com/petrnohejl/Android-Templates-And-Utilities ● In 2016: alfonz.org ● Written in Java, compatible with Kotlin ● Supports Android 4.1+ (API level 16)

Slide 3

Slide 3 text

MODULES ● adapter ● arch ● graphics ● media ● rest ● rx ● utility ● view

Slide 4

Slide 4 text

DOWNLOAD ● Source code & documentation: http://alfonz.org ● Gradle dependencies: implementation "org.alfonz:alfonz-adapter:0.8.0" implementation "org.alfonz:alfonz-arch:0.8.0" implementation "org.alfonz:alfonz-graphics:0.8.0" implementation "org.alfonz:alfonz-media:0.8.0" implementation "org.alfonz:alfonz-rest:0.8.0" implementation "org.alfonz:alfonz-rx:0.8.0" implementation "org.alfonz:alfonz-utility:0.8.0" implementation "org.alfonz:alfonz-view:0.8.0"

Slide 5

Slide 5 text

ARCHITECTURE

Slide 6

Slide 6 text

6 YEARS AGO

Slide 7

Slide 7 text

2 YEARS AGO ● MVVM with data binding ● AndroidViewModel library from Inloop Source: https://msdn.microsoft.com/en-us/library/gg405484.aspx

Slide 8

Slide 8 text

ISSUE #1: ONE TO MANY ● One VM class (type) can be declared in many V classes (types) ● One VM instance can be used with just one V instance ● I want to have one VM instance which could be shared with many V instances class UserListViewModel extends AbstractViewModel class UserListFragment extends ViewModelBaseFragment

Slide 9

Slide 9 text

ISSUE #2: VM INSTANTIATION ● No control over creating new VM instances ● No DI support ● I want to define constructors in VM and have a control over creating VM instances @Override public Class getViewModelClass() { return UserListViewModel.class; }

Slide 10

Slide 10 text

ISSUE #3: VM→V CONNECTION ● VM has a reference to V ● By MVVM definition: VM should have no info about V ● I want to have a VM completely independent from V if(getView() != null) { getView().showUsers(userList); }

Slide 11

Slide 11 text

ARCHITECTURE REQUIREMENTS ● Persistent VM ● Data binding support ● One to many relation between VM & V (VM scope) ● Control over creating new VM instances (DI support) ● V has a reference to VM, VM has no idea about V ● Resolve communication VM → V (events) ● Resolve caching UI commands when Context is null ● Resolve VM lifecycle (observing lifecycle) ● Resolve passing extras, arguments and dependencies to VM (factory)

Slide 12

Slide 12 text

ARCH COMPONENTS FROM GOOGLE

Slide 13

Slide 13 text

ARCHITECTURE COMPONENTS ● Persistent VM [✔] ● Data binding support [✔] ● One to many relation between VM & V (VM scope) [✔ VM scope] ● Control over creating new VM instances (DI support) [✔ factory pattern] ● V has a reference to VM, VM has no idea about V [✔ no getView() method] ● Resolve communication VM → V (events) [✔ LiveData] ● Resolve caching UI commands when Context is null [✔ LiveData event bus] ● Resolve VM lifecycle (observing lifecycle) [✔ LifecycleObserver] ● Resolve passing extras, arguments and dependencies to VM (factory) [✔ factory pattern & constructor in VM]

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

ALFONZ ARCH MODULE

Slide 16

Slide 16 text

ALFONZ ARCH MODULE ● Wrapper for Architecture Components lib ● Simplifies implementation of MVVM ● Base classes for MVVM architecture

Slide 17

Slide 17 text

HOW TO DEAL WITH VM→V UI ACTIONS ● Event-driven communication (fire and forget, not stateful) ● No interface (interface segregation) ● LiveData API ● Run action only if Context is available and Activity is resumed ● Definition of UI action in the view layer ● Pass any object as an argument(s) ● Support multiple event types ViewModel sendEvent() View observeEvent() showToast() ToastEvent message

Slide 18

Slide 18 text

HOW TO IMPLEMENT UI ACTIONS ● LiveData event bus (LiveBus) in VM ● Map with SingleLiveEvent instances for each event type ● Event delivered even if an observer is not active ● Delivered just once ViewModel sendEvent() View observeEvent() showToast() ToastEvent message

Slide 19

Slide 19 text

public class HelloWorldFragment extends AlfonzBindingFragment implements HelloWorldView { @Override public HelloWorldViewModel setupViewModel() { return ViewModelProviders.of(this).get(HelloWorldViewModel.class); } @Override public FragmentHelloWorldBinding inflateBindingLayout(LayoutInflater inflater) { return FragmentHelloWorldBinding.inflate(inflater); } @Override public void onClick() { getViewModel().updateMessage("Hello!"); } }

Slide 20

Slide 20 text

Slide 21

Slide 21 text

public class HelloWorldViewModel extends AlfonzViewModel { public final ObservableField state = new ObservableField<>(); public final ObservableField message = new ObservableField<>(); public void loadData() { state.set(StatefulLayout.PROGRESS); // load data from data provider... } private void onLoadData(MessageEntity m) { message.set(m); if(message.get() != null) { state.set(StatefulLayout.CONTENT); } else { state.set(StatefulLayout.EMPTY); } } }

Slide 22

Slide 22 text

public class HelloWorldViewModel extends AlfonzViewModel implements LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_START) public void onStart() { if(message.get() == null) loadData(); } } // register observer in Fragment getLifecycle().addObserver(viewModel);

Slide 23

Slide 23 text

public class SnackbarEvent extends Event { public final String message; public SnackbarEvent(String message) { this.message = message; } } // observe event in Fragment getViewModel().observeEvent(this, SnackbarEvent.class, snackbarEvent -> showSnackbar(snackbarEvent.message)); // send event in ViewModel sendEvent(new SnackbarEvent(message));

Slide 24

Slide 24 text

ALFONZ ADAPTER MODULE

Slide 25

Slide 25 text

ALFONZ ADAPTER MODULE ● Generic adapters for RecyclerView or ViewPager ● Data binding support ● Similar to BindingCollectionAdapter from Tatarka ALFONZ OR TATARKA?

Slide 26

Slide 26 text

GENERIC ADAPTERS ● SimpleDataBoundPagerAdapter ● SimpleDataBoundRecyclerAdapter ● SimpleMapDataBoundRecyclerAdapter ● MultiDataBoundPagerAdapter ● MultiDataBoundRecyclerAdapter ● MultiMapDataBoundRecyclerAdapter

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

public class ProductListAdapter extends SimpleDataBoundRecyclerAdapter { public ProductListAdapter(ProductListView view, ProductListViewModel viewModel) { super(R.layout.fragment_product_list_item, view, viewModel.products); } }

Slide 29

Slide 29 text

Slide 30

Slide 30 text

Slide 31

Slide 31 text

ALFONZ REST MODULE

Slide 32

Slide 32 text

ALFONZ REST MODULE ● Helper classes for managing REST API calls ● Wrapper for Retrofit lib ● Handling responses, errors, exceptions and logging results

Slide 33

Slide 33 text

public static final String MESSAGE_CALL_TYPE = "message"; public interface ChatService { @GET("message/{id}") Single> message(@Path("id") String id, @Query("lang") String lang); }

Slide 34

Slide 34 text

public class RestHttpException extends HttpException { public RestHttpException(Response response) { super(response); } @Override public Object parseError(Response response) { Converter converter = ... return converter.convert(response.errorBody()); } }

Slide 35

Slide 35 text

public class RestResponseHandler implements ResponseHandler { @Override public boolean isSuccess(Response response) { return response.isSuccessful(); // 2xx } @Override public String getErrorMessage(HttpException exception) { ... } @Override public String getFailMessage(Throwable throwable) { ... } @Override public HttpException createHttpException(Response response) { return new RestHttpException(response); } }

Slide 36

Slide 36 text

public class RestHttpLogger implements HttpLogger { @Override public void logSuccess(String message) { ... } @Override public void logError(String message) { ... } @Override public void logFail(String message) { ... } }

Slide 37

Slide 37 text

// in ViewModel or Repository private RestRxManager restRxManager = new RestRxManager( new RestResponseHandler(), new RestHttpLogger()); private void runMessageCall() { String callType = ChatProvider.MESSAGE_CALL_TYPE; if(!restRxManager.isRunning(callType)) { Single> rawSingle = ChatProvider.getService().message("8", "en"); Single> single = restRxManager.setupRestSingleWithSchedulers(rawSingle, callType); single.subscribeWith(createMessageObserver()); } } // in onCleared() restRxManager.disposeAll();

Slide 38

Slide 38 text

SUMMARY

Slide 39

Slide 39 text

3 RECOMMENDATIONS FOR REDUCING BOILERPLATE 1. Architecture Components 2. Data Binding 3. Generic Adapters Interested in Alfonz? See http://alfonz.org

Slide 40

Slide 40 text

THANK YOU @petrnohejl, @strvcom

Slide 41

Slide 41 text

QUESTIONS

Slide 42

Slide 42 text

No content