18

MVVM architecture,

this is my View (Activity):

private MyApp app;
private MainActivityVM viewModel;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
app = (MyApp) this.getApplication();
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

MainActivityVM.Factory factory = new MainActivityVM.Factory(app);

final MainActivityVM model = ViewModelProviders.of(this, factory)
.get(MainActivityVM.class);

viewModel = model;
binding.setVm(viewModel);
viewModel.onCreate();

and View Model:

public class MainActivityVM extends AndroidViewModel implements ViewModel {

    public MainActivityVM(@NonNull Application application) {
        super(application);
    }

    @Override public void onCreate() {
         model = new MyService();
         model.getData(); /* <-- how do i pass the activity here? */
    }

    @Override public void onPause() { }

    @Override public void onResume() { }

    @Override public void onDestroy() { }

    public static class Factory extends ViewModelProvider.NewInstanceFactory {

        @NonNull
        private final Application mApplication;

        public Factory(@NonNull Application application) {
            mApplication = application;
        }

        @Override
        public <T extends android.arch.lifecycle.ViewModel> T create(Class<T> modelClass) {
            return (T) new MainActivityVM(mApplication);
        }
    }
}

and Model:

public class myService{

    public getData(){
        if(permissionacquired(){
            getdata()
        }else{
            requestPermission();
        }
    }

    private void requestPermission() {
        PermissionKey permKey = new PermissionKey(HealthConstants.StepCount.HEALTH_DATA_TYPE, PermissionType.READ);
        HealthPermissionManager pmsManager = new HealthPermissionManager(mStore);
        try {
            // Show user permission UI for allowing user to change options

            /* BELOW CODE REQUIRE Activity reference to PASS */

            pmsManager.requestPermissions(Collections.singleton(permKey), MainActivity.this).setResultListener(result -> {

            /* ABOVE CODE REQUIRE Activity reference to PASS */

            Log.d(APP_TAG, "Permission callback is received.");
            Map<PermissionKey, Boolean> resultMap = result.getResultMap();

                if (resultMap.containsValue(Boolean.FALSE)) {
                    updateStepCountView("");
                    showPermissionAlarmDialog();
                } else {
                    // Get the current step count and display it
                    mReporter.start(mStepCountObserver);
                }
            });
        } catch (Exception e) { Log.e(APP_TAG, "Permission setting fails.", e); }
    }

}

EDIT: if you see my request permission in my Model, the API require activity to be pass - how can i pass activity reference to the request permission?

I have a get permission method that comes from Model. this get permission method from my service provider require activity e.g. requestPermission(Activity)

so in my ModelView, i have the model object which is the dataService from another source.

then, how I can reference Activity in my ViewModel so I can call: model.requestPermission(Activity); in my ViewModel?

understanding from here that:

Caution: A ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity context.

AnD
  • 3,060
  • 8
  • 35
  • 63
  • if u will use AndroidViewModel instead of ViewModel then u can get context of application – Mohd Saquib Jan 23 '18 at 10:11
  • 2
    Why do you need the `Activity` to be passed to the service provider? If you need a `Context` just to do an API call, find a way to pass the `Context` to the provider by it's creation (use the application context). If you need the `Activity` for visible output of your response, you should receive your response in the ViewModel and pass it then to your Activity (e.g. using data binding). Post an example code, how you make and handle requests now. – artkoenig Jan 23 '18 at 10:36
  • Thanks, i'm not free to ppst the code, i will do it later and update my question. Posting the activity to the servive provider is the provider Api requirement (not my code) - the request permission will pop up dialogs to request permission to user, that dialog was came from 3rd party service provider. – AnD Jan 24 '18 at 00:33
  • @ArtjomKönig I have updated the question with my code – AnD Jan 25 '18 at 01:43
  • IMHO you are overengineering your app architecture. Just put your `HealthPermissionManager` stuff in the `Activity`. Try to reduce your classes to the `Activity` and the `ViewModel` only, [keep it stupid simple](https://en.wikipedia.org/wiki/KISS_principle). – artkoenig Jan 25 '18 at 08:40
  • That's make sense, let me try it - Thanks! – AnD Jan 25 '18 at 13:02
  • Just an idea (maybe the bad one), but if you really need to pass activity, why not to do this as parameter? In this case you have your activity,but don't have any reference in your vm. – Tima May 15 '18 at 06:43

2 Answers2

1

As long as you require permission in onCreate() method you can just move logic with permission request into the activity, and pass request result into viewModel.

Karol Kulbaka
  • 1,136
  • 11
  • 21
0

In my case I also added Activity into ViewModel for permissions and strings, but it's not a good idea. When I disabled location permission in one Fragment, an application crashed, because it restarted, then restored FragmentManager with fragment stack and later started MainActivity. So ViewModel got location status too early (in constructor) and threw an exception. But when I moved getting location status to a function, then the application restarted normally.

So, using Dagger, you can write something like:

AppModule:
    @JvmStatic
    @Provides
    fun provideActivity(app: MainApplication): AppCompatActivity = app.mainActivity

In MainApplication hold mainActivity and in MainActivity set in onCreate:

application.mainActivity = this

In onDestroy:

application.mainActivity = null

In any ViewModel add:

class SomeViewModel @Inject constructor(
    private val activity: Provider<AppCompatActivity>
)

Then use it: activity.get().getString(R.string.some_string).

CoolMind
  • 26,736
  • 15
  • 188
  • 224