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

Alfonz

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.
Avatar for petrnohejl 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.

Avatar for petrnohejl

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