Best practice for Android MVVM startActivity

前端 未结 6 1244
你的背包
你的背包 2021-01-30 13:16

I am building an Android App using MVVM and DataBinding. And I have a function inside my ViewModel that starts an Activity. Is it okay to have an onClick call inside a ViewModel

相关标签:
6条回答
  • 2021-01-30 13:26

    That's absolutely perfect to put it inside ViewModel, however you need to set your ViewModel from Activity/Fragment.

    Here are some links you can follow to learn MVVM architecture.

    Approaching Android with MVVM
    Android MVVM
    https://github.com/ivacf/archi
    People-MVVM
    MVVM on Android: What You Need to Know

    0 讨论(0)
  • 2021-01-30 13:30

    As the principle of MVVM points out that only View (activity/fragment) holds reference to the ViewModel and the ViewModel shouldn't hold reference to any View.

    In your case, to start an activity, I will do like this:

    MyViewModel.class

    public class MyViewModel {
    public static final int START_SOME_ACTIVITY = 123;
    
     @Bindable
     private int messageId;
    
     public void onClick() {
      messageId = START_SOME_ACTIVITY;
      notifyPropertyChanged(BR.messageId); //BR class is automatically generated when you rebuild the project
     }
    
     public int getMessageId() {
            return messageId;
     }
    
     public void setMessageId(int message) {
            this.messageId = messageId;
     }
    
    }
    

    And in your MainActivity.class

    @BindingAdapter({"showMessage"})
    public static void runMe(View view, int messageId) {
        if (messageId == Consts.START_SOME_ACTIVITY) {      
            view.getContext().startActivity(new Intent(view.getContext(), SomeActivity.class));      
        }
    }
    
    @Override
    protected void onPause() {
        super.onPause();
        finish(); //only call if you want to clear this activity after go to other activity
    }
    

    finally, in your activity_main.xml

    <Button    
      android:onClick="@{()-> myViewModel.onClick()}"    
      bind:showMessage="@{myViewModel.messageId}" />
    
    0 讨论(0)
  • 2021-01-30 13:36

    in MVVM we can use LiveData for this Event . because ViewModel is Alive when the activity/Fragment destroyed! So the best way is LiveData

    1.Create Class Call Event and Extends It from ViewModel:

    class Event : ViewModel() {
    

    2.create field from LiveData :

    private val _showSignIn = MutableLiveData<Boolean?>()
    

    3.create method for this private field:

    val showSignIn: LiveData<Boolean?>
        get() = _showSignIn
    

    4.create method that you can setValue on your liveData:

     fun callSignIn() {
            _showSignIn.value = true
        }
    

    The final Event Class :

    import androidx.lifecycle.LiveData
    import androidx.lifecycle.MutableLiveData
    import androidx.lifecycle.ViewModel
    
    class Event : ViewModel() {
    
         private val _showSignIn = MutableLiveData<Boolean?>()
    
            val showSignIn: LiveData<Boolean?>
                get() = _showSignIn
    
            fun callSignIn() {
                _showSignIn.value = true
            }
    
    1. Call the method in your activity or fragment :

    THe instance from eventViewModel :

     private val eventViewModel = Event()
    

    call the observe :

     eventViewModel.showSignIn.observe(this, Observer {
                startActivity(Intent(this, MainActivity::class.java))
            })
    

    and if you use the data binding you can call callSignIn() in onClick XML :

    in Variable tag:

    <variable
                name="eventViewModel"
                type=packageName.Event" />
    
     android:onClick="@{() -> eventViewModel.callSignIn()}" 
    

    NOTE: do not forget set binding in your activity/fragment :

      binding.eventViewModel = eventViewModel
    

    I search for the best way and Find it. I hope to help someone

    0 讨论(0)
  • 2021-01-30 13:40

    The way I do it is, in your ViewModel:

    val activityToStart = MutableLiveData<Pair<KClass<*>, Bundle?>>()
    

    This allows you to check the class of Activity started, and the data passed in the Bundle. Then, in your Activity, you can add this code:

    viewModel.activityToStart.observe(this, Observer { value ->
        val intent = Intent(this, value.first.java)
        if(value.second != null)
            intent.putExtras(value.second)
        startActivity(intent)
    })
    
    0 讨论(0)
  • 2021-01-30 13:48

    The answer to your question is what is your goal?

    If you want to use MVVM for separation of concerns so that you can unit test your Viewmodel then you should try to keep everything that requires a Context separate from your Viewmodel. The Viewmodel contains the core business logic of your app and should have no external dependencies.

    However I like where you are going :) If the decision which Activity is opened lies in the View, then it is very very hard to write a JUnit test for it. However you can pass an object into the Viewmodel which performs the startActivity() call. Now in your Unit test you can simply mock this object and verify that the correct Activity is opened

    0 讨论(0)
  • 2021-01-30 13:53

    As per the data binding documentation. There are 2 ways to do that:

    1- MethodReferences : You have to pass the view as a parameter to the function, or you will get a compile time error.
    If you will use this way do a separate class as example here that handle such events.

    MyHandler

    public class MyHandler {
       public void onClick(View view, long productId) {
            Context context = view.getContext();
            Intent intent = new Intent(context, ProductDetailActivity.class);
            intent.putExtra("productId", productId);
            context.startActivity(intent);
        }
    }
    

    XML

    <data>
            <variable
                name="viewModel"
                type="com.example.ViewModel"
    
            <variable
                name="myHandler"
                type="com.example.MyHandler" />
    
        </data>android:onClick="@{myHandler.onClick(viewModel.product.id)}">
    

    2- Listener bindings : You don't need to pass the view as the example here.
    But if you want to startActivity make your viewModel extends AndroidViewModel and you will use the applicaion object.

    ViewModel

    public class MyViewModel extends AndroidViewModel {
        public void onClick(long productId) {
            Intent intent = new Intent(getApplication(), ProductDetailActivity.class);
            intent.putExtra("productId", productId);
            context.startActivity(intent);
        }
    }
    

    XML

    android:onClick="@{() -> viewModel.onClick(viewModel.product.id)}">
    
    0 讨论(0)
提交回复
热议问题