Giter Site home page Giter Site logo

inloop / androidviewmodel Goto Github PK

View Code? Open in Web Editor NEW
815.0 55.0 111.0 984 KB

Separating data and state handling from Fragments or Activities without lots of boilerplate-code.

License: Apache License 2.0

Java 86.10% FreeMarker 13.90%
android android-library mvvm architecture java

androidviewmodel's Introduction

AndroidViewModel

Important notice: Deprecated

This library served it's purpose for over 3 years. We believe that Google's Android Architecture Components are the preferred setup now for new projects. INLOOPX is dedicated to continue maintaining this library (no deadline on support end). So rest assured that your existing projects don't need be migrated from AndroidViewModel because of this deprecation. We are only stopping new feature development and don't recommend using it for new projects.

Separating data and state handling from Fragments or Activities without lots of boilerplate-code. Reducing them to simple dumb views.

Basic idea behind this library. An instance of a ViewModel class is assigned to your Fragment or Activity during the first creation and is kept during it's life cycle, even between display orientation changes. The ViewModel instance is removed after the Fragment or Activity is completely gone (finished, popped from backstack, replaced without keeping it in backstack).

You can execute asynchronous tasks in this ViewModel instance and this class is not destroyed during orientation change. All data handling and state logic should be placed inside this class. The Fragment or Activity is just a "dumb" view.

How to implement

  1. Create an interface for your View by extending IView. We will call it IUserListView for this example.

     public interface IUserListView extends IView {
          public void showUsers(List<User> users);
     }
  2. Create your ViewModel class by extending AbstractViewModel. For example:

    public class UserListViewModel extends AbstractViewModel<IUserListView> {
       ....
    }
  3. Each Fragment or Activity that you would like to associate with a ViewModel will need either to extend ViewModelActivityBase/ViewModelBaseFragment or copy the implementation from these classes to your base activity/fragment class (in case you can't inherit directly). For example:

    public class UserListFragment extends ViewModelBaseFragment<IUserListView, UserListViewModel> 
       implements IUserListView {
       
    }
  4. Also each Fragment or Activity has to call setModelView() after the View (Fragment/Activity) was created and initialized. This is usually on the end of onViewCreated (or onCreate in case of an Activity)

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         ButterKnife.inject(this, view);
         setModelView(this);
    }

How to use

You can forward user interaction from the View into the ViewModel simply by calling:

getViewModel().onDeleteUserClicked(userId);

The same goes for the opposite direction, when your asynchronous operation in the ViewModel finished and you would like to forward data to the View to show a list for example:

getViewOptional().showUsers(userList);

The getViewOptional() method will never return null. It will return a dummy implementation in case the View is null at the moment (e.g. Fragment already destroyed, or between orientation change). You can also check if the View is not null in case you need to:

if (getView() != null) {
    getView().showUsers(userList);
}

Your Fragment argument Bundle and Activity intent Bundle is forwarded to the ViewModel's onCreate method, which you can override to read the initial arguments for the ViewModel.

public void onCreate(Bundle arguments, Bundle savedInstanceState) {
   long userId = arguments.getInt("user_id", -1);
}

Data binding support

Data binding is supported - extend ViewModelBaseBindingFragment.java instead of ViewModelBaseFragment and implement getViewModelBindingConfig() in your Fragment.

@Override
public ViewModelBindingConfig getViewModelBindingConfig() {
   return new ViewModelBindingConfig(R.layout.fragment_sample_binding, requireActivity());
}

That's it. You can then directly use ObservableField in your ViewModels. See example.

Special handling for FragmentStatePagerAdapter

The Android implementation of FragmentStatePagerAdapter is removing Fragments and storing their state. This is in contrast with FragmentPagerAdapter where the Fragments are just detached but not removed. We should be also removing ViewModels and storing their state to be consistent with this behaviour.

Use ViewModelStatePagerAdapter instead of the default FragmentStatePagerAdapter. This class is only overriding the destroyItem() method and making sure that ViewModel is removed. The state is stored/restored automatically. You can also use the standard FragmentStatePagerAdapter - in that case ViewModels will be kept in memory and removed only when you leave the screen (Activity finished or Fragment removed).

How does it work?

A unique global ID is generated for the first time your Fragment or Activity is shown. This ID is passed on during orientation changes. Opening another instance of the same Fragment or Activity will result in a different ID. The ID is unique screen identifier. A ViewModel class is created and bound to this ID. The corresponding ViewModel instance is attached to your Fragment or Activity after an orientation change or if you return to the fragment in the back stack. The ViewModel is discarded once the Fragment/Activity is not reachable anymore (activity is finished or fragment permanently removed).

Download

compile 'eu.inloop:androidviewmodel:1.4.0'

Android Studio Template

For faster creating new screens, you can use Android Studio Template

Android Studio Template Window

Install template

Manually:

Copy the template folder to Android Studio templates folder (/Applications/Android Studio.app/Contents/plugins/android/lib/templates/others on Mac)

Automatically:

Run the following command to download and install the template automatically (Mac only)

curl -o androidviewmodel.zip -Lk https://github.com/inloop/AndroidViewModel/archive/master.zip && unzip androidviewmodel.zip && cp -af AndroidViewModel-master/template/AVM_Inloop/. "/Applications/Android Studio.app/Contents/plugins/android/lib/templates/other/AVM_Inloop" && rm -r AndroidViewModel-master && rm androidviewmodel.zip

Don't forget to restart the Android Studio.

Usage

In the Android Studio right click inside the Projet window and select File > New > AndroidViewModel Inloop > AVM Fragment

Android Studio New Template

androidviewmodel's People

Contributors

martinadamek avatar mpeng3 avatar murtazaakbari avatar rudolfpetras avatar stepansanda avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

androidviewmodel's Issues

Communication between ViewModel and Model

Hi there,
Glad to see this library handles Android life cycle quite well and AndroidViewModel does prove itself a great VM framework.

From the architecture diagram, I was wondering is it possible the ViewModel would deal with business Model instead of actual data component(DB, WebService...). If possible, how can I extend the existing framework ? What's your opinion ?

Nested fragments support

When working with nested fragments the screen ID is not unique, as nested fragments share the same Bundle, and so the IDs override one another. I would encourage storing screenId as part of BaseViewModelActivity (or the sorts of such).

transfer this rx subscription code from View to ViewModel....

I have this code operational and running beautifully if I might add, but it's running inside the view.
Which in this case is a fragment....

From EURUSDSequenceView:

rxBus.toObserverable()//
                .compose(bindToLifecycle())
                .ofType(EURUSDPercentD.class)
                .map(new Func1<EURUSDPercentD, JSONObject>() {
                    @Override
                    public JSONObject call(final EURUSDPercentD event) {
                        return event.getEvent();
                    }
                })
                .subscribe(new Action1<JSONObject>() {
                    @Override
                    public void call(final JSONObject message) {
                        if (eurusdSeqViewIsVisible.equals("true")) {
                            incomingData = true;
                            Log.d(TAG, "From: EURUSDSeqView, Msg: " + message.toString());

......

How can I get that code operational inside the viewModel because once those streams come in there is manipulation of the view going on...visibility, changing colors, saving data to prefs/dbs etc, etc
OR
should I just leave the subscription inside the view and then call to the viewModel accordingly?

thanks!

Create UI tests

Try to research if UI testing of the library would be feasible. Tests would verify if the model is correctly retained and then removed in cases such as:

  1. Activity created and activity removed (model is removed)
  2. Activity rotated (model retained).
  3. Fragment created and then removed from backstack (model removed)
  4. Fragment created and then replaced by other fragment and not added to backstack (model removed)
  5. Fragment created and replaced with other fragment and added to backstack (model retained)
  6. Fragment rotated (model retained)
  7. Fragment rotated in backstack (model retained).

Possibly other cases...

IView implementation for an recyclerview's item

Hi, I'd like to ask, how would you implement the MVVM pattern (using Google's databinding library) on the items of the RecyclerView?

Let's say, I have a fragment containing just a RecyclerView. This is easy, (Fragment implementing let's say IMainView; with MainViewModel etc.). But imagine the items within the RecyclerView. If they have more complex layout, eg. when there is a CheckBox or a Button, I want to delegate calls from those views to something that implements the IView. Let me explain:

Say, we have this layout of the item:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="viewModel"
            type="com.example.mvvm.viewmodel.ItemViewModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="16dp">

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="4"
            android:orientation="vertical">

            <TextView
                android:id="@+id/item_title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:maxLines="1"
                android:text="@{viewModel.text}"
                android:textColor="@color/primary_text"
                android:textSize="18sp"
                tools:text="Lorem ipsum dolor sit amet" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:maxLines="1"
                android:text="@{viewModel.detail}"
                android:textColor="@color/secondary_text"
                android:textSize="14sp"
                tools:text="Lorem ipsum dolor sit amet" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:orientation="horizontal">
            <CheckBox
                android:id="@+id/checkbox"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:checked="@={viewModel.checked}"
                android:onCheckedChanged="@{(cb, isChecked) -> viewModel.changeState(isChecked)}"/>
            <Button
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="T"
                android:onClick="@{() -> viewModel.test()}"/>

        </LinearLayout>
    </LinearLayout>
</layout>

I have implemented the ItemViewModel that extends the AbstractViewModel<IItemView>; Easy. The problem is that I do not know where should I implement that IItemView interface. I cannot do such thing in the fragment nor the activity. I saw that Petr Nohejl dealt with this problem by "binding" the *Click callbacks within the ViewHolder of his adapter.

I have no doubts that this approach is correct, but I am new to the MVVM and I chose this library to take the small steps with learning it and I find the architecture in his application an overkill for what I need now.

For the end, simple example of the usage of my layout (for testing and experimental purposes):
In my ItemViewModel I have a field checked that is of type ObservableBoolean and represents what the name says. Next, in the layout I have this Button that have the onClick listener passed from the viewModel. The logic is that after I tap the Button a Snackbar would appear saying whether the checkbox is checked or not. Here is a snippet from the viewModel:

public void test() {
    getView().test(checked.get());
}

Where should I implement that view.test(boolean)? I would really appreciate any idea on what is (your personal) best way of solving this.

Thank you

More consistent naming convention

I suggest to rename some life-cycle methods in AbstractViewModel.java to make it more consistent. I am talking especially about bindView, saveState and onModelRemoved. All these methods are dependent on Activity/Fragment life-cycle and it would be very nice to follow the same naming convention as Activity/Fragment's methods do.

Methods should be prefixed with "on" and the action should be described in the present tense, not the passive (e.g. onCreate).

So I suggest to rename the methods like this:

bindView -> onBindView
Similar convention to Fragment.onCreateView or onDestroyView. Eventually it might be also "onViewBound" like Fragment.onViewCreated or onActivityCreated.

saveState -> onSaveInstanceState
To make it consistent with Activity/Fragment naming convention.

onModelRemoved -> onDestroy
It is called when the fragment or view is already gone and destroyed so this name makes sense. Also, as opposite to "onCreate", it makes more sense than "onModelRemoved". Moreover, the word "Model" in the name might be a bit confusing, because it is about ViewModel and not Model, right?

What do you say? I think these changes would make the code better readable. Thanks.

Please help : "cannot resolve symbol 'T'

Greetings! I am new to Android & trying to adopt your AndroidViewModel into my project. I am currently stuck on building the project because ;

@nullable
private T mView;

throws the exception "cannot resolve symbol 'T'. Could you please guide me as to what I am missing? Any help will go a long way.....

Thank you.

-Remy

jCenter deprecation

Since jCenter repository is being deprecated, do you plan to move to maven central repository, so that existing projects can keep using your library? Thanks for the info!

Context for ViewModel

Hi, thank you for the lib. I am wondering, how I can access to associated Activity/Fragment from ViewModel class.

Imagine this simple use case:

  1. Activity/Fragment has some list of items
  2. Clicking on an item should open a new detail activity
  3. Clicking on an item is handled by Activity/Fragment and is delegated to the associated ViewModel class.
  4. The associated ViewModel class should open a new detail activity of the selected item. But this action requires a Context class.

How would you solve this? Or should "start a detail activity" action be delegated back to the activity/fragment via getView().startDetailScreen()?

ViewModel object creation - use Dagger 2

Hi,

When you want to use this library with some kind of dependency injection aka Dagger 2, you have no control over creating instance of ViewModel.

So I've made a little change in API. Instead of having abstract getViewModelClass(), i'm having createViewModel() defined on fragments / activities. Then i can use whatever I want to create my ViewModel object. This is good for IOC as well as having non default construtors etc.

This could be also done with some hard tunning ViewModelProvider (private constructor there), but this change to API looks way better for me. You can check it out at dozd@827b494

How to get or inject context in viewmodel...

I would I inject or get the context in the viewmodel.

There will be lots of statements and commands I will want to invoke in the viewmodel that
will need a context.

For instance this statement:

((App) getApplication()).inject(this);

How would I get that going in the viewmodel?

thanks!

Generic view model

A quick fix for ProxyViewHelper for those of us that failed to create activities or fragments with generic types:

public class ProxyViewHelper {
  @Nullable
  public static Class<?> getGenericType(@NonNull Class<?> in, @NonNull Class<?> whichExtends) {
    final Type genericSuperclass = in.getGenericSuperclass();
    if (genericSuperclass instanceof ParameterizedType) {
      final Type[] typeArgs = ((ParameterizedType) genericSuperclass).getActualTypeArguments();
      for (Type arg : typeArgs) {
        if (arg instanceof ParameterizedType) {
          arg = ((ParameterizedType) arg).getRawType();
        }
        if (arg instanceof Class<?>) {
          final Class<?> argClass = (Class<?>) arg;
          if (whichExtends.isAssignableFrom(argClass)) {
            return argClass;
          }
        }
      }
    }
    return null;
  }
}

Documentation about inter-view-model communication

Hello.

Could you please describe on the wiki what is the preferred way to communicate between view-models for this library? Especially the important part is how to abstract communication with view-model lifecycle, e.g. how pass data and messages to view-models that have not been instantiated yet?

A straighforward solution is to store data that needs to be passed from one view-model to another to shared preferences or SQLite database that are then loaded when the target view-model is instantiated and send events via broadcasts but I don't see this as a nice and clean solution.

Another option is to use observable pattern managed in static Application context but there is no option to keep observable object state when application object is terminated.

Thank you for the library anyway!

AVM in Activity issue

Is fragment required for AVM to work? I followed all the steps and all works in Fragments but I created one ResetPasswordActivity which does not use a fragment and AVM is failing there.

public class ResetPasswordActivity extends BaseActivity{
...

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_reset_password);
        ButterKnife.bind(this);
        setModelView(this);
        getViewModel().setEmail(email); // setEmail method not found
    }
}

@Override
    public Class<ResetPasswordViewModel> getViewModelClass() {
        return ResetPasswordViewModel.class;
    }

BaseActivity:

public abstract class BaseActivity extends ViewModelBaseActivity

ResetPasswordViewModel

public class ResetPasswordViewModel extends AbstractViewModel implements ResetPasswordViewModelInputs {}

Have I forgotten anything?

changing setModelView to onStart in sample

If the setModelView() is called inside onViewCreated(),the changes made to the View in the bindView method will be discarded when the View State is Restored.
changing setModelView() to onStart method will solve the issue.

View and ViewModel as a parent class

Is it possible to have a View and a ViewModel which act as a parent class to a subclass View and ViewModel where the parent class and the subclass define different contracts (IViews)?

I tried to do that but without success.

Get rid of onRetainNonConfigurationInstance

onRetainNonConfigurationInstance is deprecated since API 13, this should be replaced with a headless fragment with setRetainInstance(true) or the models should be kept in a singleton model manager.

Android Studio custom template

It would be useful to have custom template for Android Studio something like this so you could simply generate Fragment/Activity (maybe option to generate newInstance for fragment), Model (with boilerplate code) and IView.

Checking for PlayServices

Since there is no onResume() in the ViewModel, what is the correct way to check if PlayServices is available?

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.