Android ViewModel and startActivity
Asked Answered
L

5

27

I am learning ViewModel and LiveData and, in the process, a doubt arose.

What should I do if I need to start an Activity?

Is it ok to pass the context as a parameter to the ViewModel (the context will not be stored inside the ViewModel)?

ActivityAViewModel : ViewModel() {
    // ...

    fun openActivityB(context: Context) {
        context.startActivity(...)
    }

    // ...
}

ActivityA {
    // ...

    fun onSomethingHappened() {
        viewModel.openActivityB(this)
    }

    // ...
}

If not, what is the most correct thing to do in that case?

Laurenelaurens answered 21/2, 2019 at 19:12 Comment(0)
J
22

I like firing Events. :D

As everyone says ViewModel should not contain Context or reference to classes that contain Context. So it is not a good idea to do startActivity from ViewModel.

What I would do is have a LiveData containing data for an event. This event will be fired from your ViewModel based on your business logic (Maybe you are showing a CountDown and at the end of it you move to the next Activity?). It is a LiveData and you can observe on it. Based on the data of this event you can start your activity.

You may want to look at SingleLiveEvent

Jaws answered 25/2, 2019 at 18:1 Comment(1)
the link is brokenPatentor
L
17

You should call startActivity from activity, not from viewmodel. If you want to open it from viewmodel, you need to create livedata in viewmodel with some navigation parameter and observe on livedata inside the activity.

Loveinidleness answered 21/2, 2019 at 19:26 Comment(1)
Thank you very much for your answer, Alexandr! A code as an example would be very welcome :)Laurenelaurens
K
8

IMHO, viewmodel should know nothing about view and how it presents info to user.

/**
 * Activity (as view) responsible only for gathering actions and intentions from user and
 * show result state.
 * View must know "What user want". View knows meaning its interface.
 * Click on button 'login' means INTENTION to login somewhere.
 * This intention pass to ViewModel to process it and wait some changing state from LiveData.
 * For example implemented as Actions.
 */
public class LoginActivity extends AppCompatActivity {
    private LoginViewModel mLoginViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mLoginViewModel = ViewModelProviders.of(this).get(LoginViewModel.class);
        mLoginViewModel.getAction().observe(this, new Observer<Action>() {
            @Override
            public void onChanged(@Nullable final Action action) {
                if(action != null){
                    handleAction(action);
                }
            }
        });

        //Emulate user intention
        mLoginViewModel.userWantToLogin("0123456789", "admin");
    }

    private void handleAction(@NonNull final Action action) {
        switch (action.getValue()){
            case Action.SHOW_WELCOME:
                //show Activity. 
                break;
            case Action.SHOW_INVALID_PASSWARD_OR_LOGIN:
                //show Toast
                break;
        }
    }
}

    public class LoginViewModel extends ViewModel {
        //Stores actions for view.
        private MutableLiveData<Action> mAction = new MutableLiveData<>();

        public LiveData<Action> getAction() {
            return mAction;
        }

        /**
         * Takes intention to login from user and process it.
         *
         * @param password Dummy password.
         * @param login Dummy login.
         */
        public void userWantToLogin(String password, String login){
            if(validateInfo(password, login)){
                showWelcomeScreen();
            }else {
                showPasswordOrLoginInvalid();
            }
        }

        /*
         * Changes LiveData. Does not act directly with view.
         * View can implement any way to show info
          * to user (show new activity, alert or toast)
         */
        private void showPasswordOrLoginInvalid() {
            mAction.setValue(new Action(Action.SHOW_INVALID_PASSWARD_OR_LOGIN));
        }

        /*
         * Changes LiveData. Does not act directly with view.
         * View can implement any way to show info
         * to user (show new activity, alert or toast)
         */
        private void showWelcomeScreen() {
            mAction.setValue(new Action(Action.SHOW_WELCOME));
        }

        //As example of some logic.
        private boolean validateInfo(String password, String login) {
            return password.equals("0123456789") && login.equals("admin");
        }
    }

public class Action {
    public static final int SHOW_WELCOME = 0;
    public static final int SHOW_INVALID_PASSWARD_OR_LOGIN = 1;
    private final int mAction;

    public Action(int action) {
        mAction = action;
    }

    public int getValue() {
        return mAction;
    }
}
Kenogenesis answered 21/2, 2019 at 22:4 Comment(1)
Thank you, @no_cola. Your answer guided me to find the following link and implement its solution: medium.com/androiddevelopers/…Laurenelaurens
M
2

It would be a good design choice if the viewmodel knows nothing about the activities. Basically, viewmodel and activities play observable and observers roles. ViewModel, being a wrapper around your repository or business model or orchestration layer, provides the reactive style data streaming and plays observable role. It means, several activities or fragments, being observers, can listen to one view model.

So it is better to keep louse coupling, by not tightening the one particular activity to one view model but it is common convention among mobile developers that they prefer to create one view model to one activity/fragment.

If you have retrofit or okhttp or other libraries that need context, pass them context thru dagger2 or Koin DI libraries. It would be a clean architecture.

Metachromatism answered 22/2, 2019 at 0:4 Comment(0)
A
0

You can use an Application context which is provided by the AndroidViewModel, you should extend AndroidViewModel which is simply a ViewModel that includes an Application reference.

Aplacental answered 21/2, 2019 at 20:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.