Share
Embed
Alfonz
Talk about Alfonz library given at STRV Android Meetup in Nov/2017. Alfonz is a multi purpose library which helps to build Android app, makes the development process easier and helps to avoid boilerplate code. More info at http://alfonz.org.
petrnohejl
November 28, 2017
Transcript
42 slides
-
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) -
MODULES • adapter • arch • graphics • media •
rest • rx • utility • view -
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" -
2 YEARS AGO • MVVM with data binding • AndroidViewModel
library from Inloop Source: https://msdn.microsoft.com/en-us/library/gg405484.aspx -
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<IUserListView> class UserListFragment extends ViewModelBaseFragment<IUserListView, UserListViewModel> -
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<UserListViewModel> getViewModelClass() { return UserListViewModel.class; } -
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); } -
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) -
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] -
ALFONZ ARCH MODULE • Wrapper for Architecture Components lib •
Simplifies implementation of MVVM • Base classes for MVVM architecture -
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 -
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 -
public class HelloWorldFragment extends AlfonzBindingFragment<HelloWorldViewModel, FragmentHelloWorldBinding> 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!"); } } -
<layout> <data> <variable name="view" type="com.example.ui.HelloWorldView" /> <variable name="viewModel" type="com.example.viewmodel.HelloWorldViewModel" />
</data> <org.alfonz.view.StatefulLayout ... app:state="@{viewModel.state}"> <TextView ... android:text="@{viewModel.message.text}" android:onClick="@{() -> view.onClick()}" /> </org.alfonz.view.StatefulLayout> </layout> -
public class HelloWorldViewModel extends AlfonzViewModel { public final ObservableField<Integer> state
= new ObservableField<>(); public final ObservableField<MessageEntity> 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); } } } -
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); -
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)); -
ALFONZ ADAPTER MODULE • Generic adapters for RecyclerView or ViewPager
• Data binding support • Similar to BindingCollectionAdapter from Tatarka ALFONZ OR TATARKA? -
GENERIC ADAPTERS • SimpleDataBoundPagerAdapter • SimpleDataBoundRecyclerAdapter • SimpleMapDataBoundRecyclerAdapter • MultiDataBoundPagerAdapter
• MultiDataBoundRecyclerAdapter • MultiMapDataBoundRecyclerAdapter -
public class ProductListAdapter extends SimpleDataBoundRecyclerAdapter { public ProductListAdapter(ProductListView view, ProductListViewModel
viewModel) { super(R.layout.fragment_product_list_item, view, viewModel.products); } } -
<layout> <data> <variable name="view" type="com.example.ui.ProductListView" /> <variable name="data" type="com.example.entity.ProductEntity" />
</data> <LinearLayout ... android:onClick="@{() -> view.onItemClick(data)}"> <TextView ... android:text="@{data.name}" /> </LinearLayout> </layout> -
ALFONZ REST MODULE • Helper classes for managing REST API
calls • Wrapper for Retrofit lib • Handling responses, errors, exceptions and logging results -
public static final String MESSAGE_CALL_TYPE = "message"; public interface ChatService
{ @GET("message/{id}") Single<Response<MessageEntity>> message(@Path("id") String id, @Query("lang") String lang); } -
public class RestHttpException extends HttpException { public RestHttpException(Response<?> response) {
super(response); } @Override public Object parseError(Response<?> response) { Converter<ResponseBody, ErrorEntity> converter = ... return converter.convert(response.errorBody()); } } -
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); } } -
public class RestHttpLogger implements HttpLogger { @Override public void logSuccess(String
message) { ... } @Override public void logError(String message) { ... } @Override public void logFail(String message) { ... } } -
// 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<Response<MessageEntity>> rawSingle = ChatProvider.getService().message("8", "en"); Single<Response<MessageEntity>> single = restRxManager.setupRestSingleWithSchedulers(rawSingle, callType); single.subscribeWith(createMessageObserver()); } } // in onCleared() restRxManager.disposeAll(); -
3 RECOMMENDATIONS FOR REDUCING BOILERPLATE 1. Architecture Components 2. Data
Binding 3. Generic Adapters Interested in Alfonz? See http://alfonz.org