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

Alfonz

petrnohejl
November 28, 2017

 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
Tweet

More Decks by petrnohejl

Other Decks in Programming

Transcript

  1. 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)
  2. 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"
  3. 2 YEARS AGO • MVVM with data binding • AndroidViewModel

    library from Inloop Source: https://msdn.microsoft.com/en-us/library/gg405484.aspx
  4. 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>
  5. 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; }
  6. 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); }
  7. 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)
  8. 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]
  9. ALFONZ ARCH MODULE • Wrapper for Architecture Components lib •

    Simplifies implementation of MVVM • Base classes for MVVM architecture
  10. 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
  11. 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
  12. 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!"); } }
  13. <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>
  14. 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); } } }
  15. 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);
  16. 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));
  17. ALFONZ ADAPTER MODULE • Generic adapters for RecyclerView or ViewPager

    • Data binding support • Similar to BindingCollectionAdapter from Tatarka ALFONZ OR TATARKA?
  18. <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>
  19. ALFONZ REST MODULE • Helper classes for managing REST API

    calls • Wrapper for Retrofit lib • Handling responses, errors, exceptions and logging results
  20. 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); }
  21. 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()); } }
  22. 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); } }
  23. public class RestHttpLogger implements HttpLogger { @Override public void logSuccess(String

    message) { ... } @Override public void logError(String message) { ... } @Override public void logFail(String message) { ... } }
  24. // 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();
  25. 3 RECOMMENDATIONS FOR REDUCING BOILERPLATE 1. Architecture Components 2. Data

    Binding 3. Generic Adapters Interested in Alfonz? See http://alfonz.org