GithubHelp home page GithubHelp logo

vicidroiddev / amalia Goto Github PK

View Code? Open in Web Editor NEW
9.0 9.0 0.0 1.93 MB

Amalia is an MVP/MVI implementation which dictates a straightforward pattern involving uni-directional stream of view states to render and ui events to process.

Home Page: https://vicidroiddev.github.io/amalia/

License: Apache License 2.0

Kotlin 99.87% Shell 0.13%
android livedata mvi-android mvi-architecture mvp-android mvp-architecture mvp-pattern

amalia's People

Contributors

vicidroiddev avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

amalia's Issues

onViewRecycled: java.lang.IndexOutOfBoundsException: Index: 13, Size: 13


java.lang.IndexOutOfBoundsException: Index: 13, Size: 13
--

1 |   | java.util.ArrayList.get(ArrayList.java:411)
2 |   | java.util.Collections$UnmodifiableList.get(Collections.java:1295)
3 |   | com.vicidroid.amalia.ui.recyclerview.adapter.DefaultRecyclerViewAdapter.onViewRecycled(DefaultRecyclerViewAdapter.kt:4)

Enhance the concept of binding to a presenter

The bind concept is too heavily tied to a view delegate. It would be better to be able to allow binding a fragment or an activity through some interface. This would allow much better integration with legacy code where one wishes to not manipulate all the view logic in the fragment but still leverage the benefits of amalia's view states.

Opening new screens from presenters with a delay is problematic.

We need a way to handle the following:

  1. presenter is notified of a view event triggered by a button click.
  2. presenter does some long running sync operation.
  3. a new activity must be opened after this long running operation.
  4. the presenter should not hold onto an activity context that may be a member variable in the view event which was originally received.

Possible ways to handle this:

  1. Use the application context and add the new task flag.
    • This is bad if you have some central logic for navigating in your app which is in a BaseActivity. We should not hold on to the activity in the view.

  2. Send a state to the view delegate Navigation(uri)
    • This is problematic because it's a one time state, it should not really be persisted. What if you rotate or do something that would cause this state to be restored? It's almost like we need some form of SingleUseAction

  3. Propagate states from your presenter. Intercept those states in a fragment or activity. This solves the concern with (1), however it brings about another annoyance. What if you have a presenter that manages several other child presenters. Say you have a dashboard with lots of presenters. Do you really want to clutter your fragment or activity with propagateStatesTo calls just to handle navigation?

Support simple navigation

Useful links to read up on:

Fragments destroy views when navigating around:
https://stackoverflow.com/questions/7536988/android-app-out-of-memory-issues-tried-everything-and-still-at-a-loss/7576275#7576275.

The basic gist is launching a bunch of activities with heavy bitmaps in them could easily cause an out of memory exception. Fragments are nice because they destroy the view, BUT fragments bring about extra complication and the headaches have been numerous (although it been getting a lot better!).

If we were to leverage a single activity architecture, it would be nice to have a navigation component that:
• can be invoked by a presenter
• likely lives in an activity
• can remove previous views for memory efficiency
• can automatically find the presenter and view delegate to load based on some key
• can automatically receive lifecycle events
• can automatically save and restore saveInstanceState
• isn't extremely bogged down by annotation processors (although it might be necessary here to annotate a presenter or view delegate)
• can process intent extras (think of incoming uris)
• likely leverages some stack consisting of data classes for future enhancement

Add automatic handling of savedInstanceState to amalia components

https://developer.android.com/reference/androidx/lifecycle/AbstractSavedStateVMFactory
Factory that accepts savedInstanceState in its constructor.
In the override for create(), need to inject savedInstanceState and call onRestoreState() in our base presenter. We should avoid adding the bundle to the constructor.
onRestoreState by default should try to restore the viewState (if it was originally parceable).

https://developer.android.com/reference/androidx/savedstate/SavedStateRegistryOwner
Implemented by fragments and activities. Need to pass this to the factory, likely in order to receive our state

Add api for tracking closeableObjects

A closable cache exists to ensure any object implementing Closable will close when the presenter is destroyed.

It is handy to track closable objections instead of manually cleaning up in onPresenterDestroyed

Currently you must interface with the concurrent hash map directly. We should hide the implementation and throw an error if a duplicate key is detected.

Abstract ViewDelegate functionality to an interface

BasePresenter should not directly interact with the BaseViewDelegate in its bind method. Instead it should be able to bind to ViewDelegate interface. This will simplify the two bind methods as well

In theory, the ViewDelegate interface can be implemented by:
View
Fragment
Activity
• delegate extending from BaseViewDelegate

The ViewDelegate interface should include:
• lifecycleowner - required
• event live data - optional?
• viewDiffLiveData - later on

Note: this will break the api if bind(lifecycleOwner) is used

Generic Recyclerview support

Given the common use-case of needing to display list items in a recyclerview it would be a great addition to have a view delegate which manages all the adapter code.

Moreover, it would be good to investigate the possibility of a complex view driven by several presenters and view delegates. Take the example of a dashboard where each card is driven by a presenter.

Single shot requests from presenter to view delegate

It is common to require a presenter to send a request to a view delegate which should not be retained after a rotation where the view is destroyed.

The view state pipeline is not ideal for temporary requests as it will retain the last pushed state and automatically send it to a new view delegate when bind is called.

Examples include:
• Tell the view delegate to show a progress dialog
• Tell the view delegate to navigate to another location after some specific amount of time.
• Tell the view delegate to update one very specific piece of its view.

A perhaps alternate situation that may benefit form a separate pipeline is noted below:

Say you have a complicated dashboard presenter that consists of several individual child presenters. Each child presenter is responsible for emitting card details. The parent presenter is responsible for aggregating all the individual card details sent by each child. The parent presenter is also responsible for packaging up card details into a single list of item to show in a list view.

Now if the dashboard presenter listens for all these states from child presenters on the main thread we may end up thread thrashing. Child presents go in the background to load data and push their states on the main thread. It would be nice if the parent present (Dashboard) could listen to one shot states sent by the child presenters in a background thread. The Dashboard Presenter could then package all those states together and even perform several list transformations off the main thread (debouncing, sorting). In the end the dashboard presenter can send the retained view state (list items loaded) to the list view delegate on the main thread.
The huge benefit here is that all the processing of child view states does not need to be retained. We just need to retain the final result.

Improve overrides in presenter for save state handling

In the past it has been somewhat unclear as to when one should perform data loading.
onBindViewDelegate is called when a new view is attached. That is a problematic place to start loading data since duplicate work will be performed on configuration changes. Recall that presenters are retained across configuration changes. While you could check for member fields being populated with needed data to avoid doing additional data loading; it's not that obvious if a new state must be pushed.

With the addition of #10 automatic saving of view states will alleviate many of the annoyances concerning manual saving of data from the fragment/activity level. As such it makes less sense to hold actual member fields in the presenter. Instead a representative snapshot in time should be built as a view state which is parcelable. Advocating to move all data in the view state then produces a problem around how to reliably know when data should be loaded.

Proposal

NEW onLoadData/ loadInitialState

  • old presenters that sent view states which where not parcelable, will receive this call after process death
  • new presenters that send view states which are parcelable, will not receive this call after process death, instead onViewStateRestored will be called
  • guaranteed to be called before onBindViewDelegate
  • represents a blank slate where no view state is available
  • old presenters which used onBindViewDelegate to spawn loading should move to this method

NEW onViewStateRestored(viewState)

  • only called when a new presenter instance is created with a parcelable view state
  • guaranteed to be called before onBindViewDelegate
  • can be used to retrieve fields from saved state handle, although it is preferable to ensure everything is in the view state to guarantee a model representing a snapshot in time.

CHANGE onViewCreated -> onViewAttached, onViewDestroyed -> onViewDetached

  • useful for enabling / disabling listeners (db callbacks, network callbacks etc..)
  • We may not necessarily destroy views but rather attach a different view, hence it makes sense to rename the method to attach/detach

CHANGE onBindViewDelegate

  • ensure no data loading is spawned from here, use onLoadData
  • this will always be called when a new view is attached, regardless of config changes or process death
  • this should only be used to bind child presenters to their corresponding delegates

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.