WhenActivated not called in ViewModel - android

I'm using ReactiveUI 8.7.2 to create android/iOS Xamarin.Native app with shared ViewModels. I want to use WhenActivated in Fragments and corresponding ViewModels, but it's only being called in Fragments. Here's my code:
Base ViewModel:
public class ReactiveViewModel : ReactiveObject, ISupportsActivation
{
public ViewModelActivator Activator { get; }
public ReactiveViewModel(ViewModelActivator viewModelActivator)
{
Activator = viewModelActivator;
this.WhenActivated((Action<IDisposable> disposable) =>
{
Console.WriteLine("Activated BaseViewModel");
});
}
public ReactiveViewModel() : this(new ViewModelActivator())
{
}
}
Base Fragment:
public abstract class BaseReactiveFragment<T> : ReactiveUI.AndroidSupport.ReactiveFragment<T> where T : ReactiveViewModel
{
public BaseReactiveFragment()
{
this.WhenActivated(disposable =>
{
Console.WriteLine("Activated BaseFrament");
});
}
}
ViewModel
public class MyViewModel : ReactiveViewModel
{
public MyViewModel()
{
this.WhenActivated((Action<IDisposable> disposable) =>
{
Console.WriteLine("Activated ViewModel");
});
}
}
Fragment
public class MyFragment : BaseReactiveFragment<MyViewModel>
{
public MyFragment()
{
this.WhenActivated((Action<IDisposable> disposable)=>
{
Console.WriteLine("Activated Fragment");
});
}
}
What am I missing?

[Posting an extended version of my comment as an answer for completeness.]
Make sure you assign the ViewModel property of MyFragment/BaseReactiveFragment. The ViewModel property is only automatically assigned if you're using ReactiveUI's routing infrastructure e.g. RoutedViewHost or ViewModelViewHost. But because Android uses a different mechanism (intents) for Activity construction, ReactiveUI.Android doesn't include those helpers.
Glad to hear it was an easy fix. Happy coding!

Related

Observing viewmodel for the second time returns null in android

In my android app,im following architecture components with mvvm pattern.
my app makes a network call to display the weather information.api call is being made from repository which returns a livedata of response to the viewmodel,which inturn is observed by my main activity.
the app works fine except for one condition,whenever i disconnect the internet to test the fail case,it inflates error view as required
in the error view i have a retry button,which makes the method call to observe the viewmodel again(this method was also called by oncreate() for the first time,which worked)
even after switching on the internet,and clicking the retry button which listens for the observable.still the data becomes null.
i dont know why.please anyone help
REPOSITORY
#Singleton public class ContentRepository {
#Inject AppUtils mAppUtils;
private RESTService mApiService;
#Inject public ContentRepository(RESTService mApiService) {
this.mApiService = mApiService;
}
public MutableLiveData<ApiResponse<WeatherModel>> getWeatherListData() {
final MutableLiveData<ApiResponse<WeatherModel>> weatherListData = new MutableLiveData<>();
mApiService.getWeatherList().enqueue(new Callback<WeatherModel>() {
#Override public void onResponse(Call<WeatherModel> call, Response<WeatherModel> response) {
weatherListData.setValue(new ApiResponse<>(response.body()));
}
#Override public void onFailure(Call<WeatherModel> call, Throwable t) {
weatherListData.setValue(new ApiResponse<>(t));
}
});
return weatherListData;
}
}
VIEWMODEL
public class HomeViewModel extends AndroidViewModel {
private final LiveData<ApiResponse<WeatherModel>> weatherListObservable;
#Inject public HomeViewModel(Application application, ContentRepository contentRepository) {
super(application);
this.weatherListObservable = contentRepository.getWeatherListData();
}
public LiveData<ApiResponse<WeatherModel>> getWeatherListObservable() {
return weatherListObservable;
}
}
OBSERVE METHOD IN ACTIVITY
private void observeViewModel() {
mHomeViewModel = ViewModelProviders.of(this, mViewModelFactory).get(HomeViewModel.class);
mHomeViewModel.getWeatherListObservable().observe(this, weatherModelApiResponse -> {
if (weatherModelApiResponse.isSuccessful()) {
mErrorView.setVisibility(View.GONE);
mBinding.ivLoading.setVisibility(View.GONE);
try {
setDataToViews(weatherModelApiResponse.getData());
} catch (ParseException e) {
e.printStackTrace();
}
} else if (!weatherModelApiResponse.isSuccessful()) {
mBinding.ivLoading.setVisibility(View.GONE);
mDialogUtils.showToast(this, weatherModelApiResponse.getError().getMessage());
mErrorView.setVisibility(View.VISIBLE);
}
});
}
RETRY BUTTON IN ACTIVITY
#Override public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_retry:
mErrorView.setVisibility(View.GONE);
observeViewModel();
break;
}
}
Updated:- 5 December 2017
I was fortunate to meet Lyla Fujiwara, during Google Developer Days, India where I asked her the same question. She suggested me to user Transformations.switchMap(). Following is the updated solution -
#Singleton
public class SplashScreenViewModel extends AndroidViewModel {
private final APIClient apiClient;
// This is the observable which listens for the changes
// Using 'Void' since the get method doesn't need any parameters. If you need to pass any String, or class
// you can add that here
private MutableLiveData<Void> networkInfoObservable;
// This LiveData contains the information required to populate the UI
private LiveData<Resource<NetworkInformation>> networkInformationLiveData;
#Inject
SplashScreenViewModel(#NonNull APIClient apiClient, #NonNull Application application) {
super(application);
this.apiClient = apiClient;
// Initializing the observable with empty data
networkInfoObservable = new MutableLiveData<Void>();
// Using the Transformation switchMap to listen when the data changes happen, whenever data
// changes happen, we update the LiveData object which we are observing in the MainActivity.
networkInformationLiveData = Transformations.switchMap(networkInfoObservable, input -> apiClient.getNetworkInformation());
}
/**
* Function to get LiveData Observable for NetworkInformation class
* #return LiveData<Resource<NetworkInformation>>
*/
public LiveData<Resource<NetworkInformation>> getNetworkInfoObservable() {
return networkInformationLiveData;
}
/**
* Whenever we want to reload the networkInformationLiveData, we update the mutable LiveData's value
* which in turn calls the `Transformations.switchMap()` function and updates the data and we get
* call back
*/
public void setNetworkInformation() {
networkInfoObservable.setValue(null);
}
}
The Activity's code will be updated as -
final SplashScreenViewModel splashScreenViewModel =
ViewModelProviders.of(this, viewModelFactory).get(SplashScreenViewModel.class);
observeViewModel(splashScreenViewModel);
// This function will ensure that Transformation.switchMap() function is called
splashScreenViewModel.setNetworkInformation();
This looks the most prominent and proper solution to me for now, I will update the answer if I better solution later.
Watch her droidCon NYC video for more information on LiveData. The official Google repository for LiveData is https://github.com/googlesamples/android-architecture-components/ look for GithubBrowserSample project.
Old Code
I have not been able find a proper solution to this, but this works so far -
Declare ViewModel outside the observeViewModel() and change the function like this -
private void observeViewModel(final HomeViewModel homeViewModel) {
homeViewModel.getWeatherListObservable().observe(this, weatherModelApiResponse -> {
if (weatherModelApiResponse.isSuccessful()) {
mErrorView.setVisibility(View.GONE);
mBinding.ivLoading.setVisibility(View.GONE);
try {
setDataToViews(weatherModelApiResponse.getData());
} catch (ParseException e) {
e.printStackTrace();
}
} else if (!weatherModelApiResponse.isSuccessful()) {
mBinding.ivLoading.setVisibility(View.GONE);
mDialogUtils.showToast(this, weatherModelApiResponse.getError().getMessage());
mErrorView.setVisibility(View.VISIBLE);
}
});
}
Update HomeViewModel to -
public class HomeViewModel extends AndroidViewModel {
private final LiveData<ApiResponse<WeatherModel>> weatherListObservable;
#Inject public HomeViewModel(Application application, ContentRepository contentRepository) {
super(application);
getWeattherListData();
}
public void getWeatherListData() {
this.weatherListObservable = contentRepository.getWeatherListData();
}
public LiveData<ApiResponse<WeatherModel>> getWeatherListObservable() {
return weatherListObservable;
}
}
Now Retry button, call the observeViewModel function again and pass mHomeViewModel to it. Now you should be able to get a response.

Is it possible to enforce non-nullability of LiveData values?

Is there any way to enforce non-nullability of LiveData values? Default Observer implementation seems to have #Nullable annotation which forces an IDE to suggest that the value might be null and should be checked manually:
public interface Observer<T> {
/**
* Called when the data is changed.
* #param t The new data
*/
void onChanged(#Nullable T t);
}
A new option is available if you use Kotlin. You can replace LiveData with StateFlow. It is more suitable for Kotlin code and provides built-in null safety.
Instead of using:
class MyViewModel {
val data: LiveData<String> = MutableLiveData(null) // the compiler will allow null here!
}
class MyFragment: Fragment() {
model.data.observe(viewLifecycleOwner) {
// ...
}
}
You can use:
class MyViewModel {
val data: StateFlow<String> = MutableStateFlow(null) // compilation error!
}
class MyFragment: Fragment() {
lifecycleScope.launch {
model.data.collect {
// ...
}
}
}
StateFlow is part of coroutines and to use the lifecycleScope you need to add the lifecycle-extensions dependency:
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
Note that this API has been experimental before coroutines 1.4.0.
Here's some additional reading about replacing LiveData with StateFlow.
As Igor Bubelov pointed out, another advantage of this approach is that it's not Android specific so it can be used in shared code in multiplatform projects.
If you use Kotlin, you can create much nicer non-null observe function with extension. There is an article about it. https://medium.com/#henrytao/nonnull-livedata-with-kotlin-extension-26963ffd0333
It's possible to do it safely only if you are in control of the code which sets the data because you'll also have to wrap the LiveData class. This way the data setting methods will be protected with #NonNull and you can be sure that the data has already been checked before reaching the Observer.
Wrap the LiveData class:
public class NonNullMutableLiveData<T> extends MutableLiveData<T> implements NonNullLiveData<T> {
private final #NonNull T initialValue;
public NonNullMutableLiveData(#NonNull T initialValue) {
this.initialValue = initialValue;
}
#Override
public void postValue(#NonNull T value) {
super.postValue(value);
}
#Override
public void setValue(#NonNull T value) {
super.setValue(value);
}
#NonNull
#Override
public T getValue() {
//the only way value can be null is if the value hasn't been set yet.
//for the other cases the set and post methods perform nullability checks.
T value = super.getValue();
return value != null ? value : initialValue;
}
//convenience method
//call this method if T is a collection and you modify it's content
public void notifyContentChanged() {
postValue(getValue());
}
public void observe(#NonNull LifecycleOwner owner, #NonNull NonNullObserver<T> observer) {
super.observe(owner, observer.getObserver());
}
}
Create an interface for exposing as immutable:
public interface NonNullLiveData<T> {
#NonNull T getValue();
void observe(#NonNull LifecycleOwner owner, #NonNull NonNullObserver<T> observer);
}
Finally, wrap the Observer:
//not implementing Observer<T> to make sure this class isn't passed to
//any class other than NonNullMutableLiveData.
public abstract class NonNullObserver<T> {
public Observer<T> getObserver() {
return new ActualObserver();
}
public abstract void onValueChanged(#NonNull T t);
private class ActualObserver implements Observer<T> {
#Override
public void onChanged(#Nullable T t) {
//only called through NonNullMutableLiveData so nullability check has already been performed.
//noinspection ConstantConditions
onValueChanged(t);
}
}
}
Now you can create your data like this:
class DataSource {
private NonNullMutableLiveData<Integer> data = new NonNullMutableLiveData<>(0);
public NonNullLiveData<Integer> getData() {
return data;
}
}
And use it like this:
dataSource.getData().observe(this, new NonNullObserver<Integer>() {
#Override
public void onValueChanged(#NonNull Integer integer) {
}
});
Completely null safe.
While there a few things you can do, it is your responsibility to make sure you don't pass null to the LiveData. In addition to that, every 'solution' is more a suppression of the warning, which can be dangerous (if you do get a null value, you might not handle it and Android Studio will not warn you).
Assert
You can add assert t != null;. The assert will not be executed on Android, but Android Studio understands it.
class PrintObserver implements Observer<Integer> {
#Override
public void onChanged(#Nullable Integer integer) {
assert integer != null;
Log.d("Example", integer.toString());
}
}
Suppress the warning
Add an annotation to suppress the warning.
class PrintObserver implements Observer<Integer> {
#Override
#SuppressWarnings("ConstantConditions")
public void onChanged(#Nullable Integer integer) {
Log.d("Example", integer.toString());
}
}
Remove the annotation
This also works in my installation of Android Studio, but it might not work for you, but you could try to just remove the #Nullable annotation from the implementation:
class PrintObserver implements Observer<Integer> {
#Override
public void onChanged(Integer integer) {
Log.d("Example", integer.toString());
}
}
Default methods
It's unlikely you can use this on Android, but purely from a Java perspective, you could define a new interface and add a null check in a default method:
interface NonNullObserver<V> extends Observer<V> {
#Override
default void onChanged(#Nullable V v) {
Objects.requireNonNull(v);
onNonNullChanged(v);
// Alternatively, you could add an if check here.
}
void onNonNullChanged(#NonNull V value);
}
fun <T> LiveData<T>.observeNonNull(owner: LifecycleOwner, observer: (t: T) -> Unit) {
this.observe(owner, Observer {
it?.let(observer)
})
}
You would have to do some additional work to handle null values that come from the library itself.
For example, when you return a LiveData from a #Dao in Room, like:
#Dao interface UserDao {
#get:Query("SELECT * FROM users LIMIT 1")
val user: LiveData<User>
}
And observe the user live data, it will call the onChanged callback with a null value if there is no user.

Realm accessed from incorrect thread, with Dagger

I am currently learning Dagger, RxJava, Realm and MVP in a simple project.
Basically what this app can do is it can view, add, delete and update data from database, which I'm using Realm.
I have decided to follow MVP architecture and applied repository pattern as well for data manipulation at the back end layer.
For an extra learning, I added Dagger for the dependency injection in the architecture.
Before this, I have developed an app without applying MVP nor repository pattern, not even Dagger and RxJava in mind. All seems to work well without any errors from Realm threading system. Maybe because I tied everything in a single class.
So, now that I'm moving away from that approach, I'm now having trouble implementing it in the new approach, which I think is more loosely coupled and should be better if implemented correctly.
Enough of introduction, let's get back to the topic.
The issue I'm facing right now is Realm always giving me this error:
Exception has been thrown: Realm accessed from incorrect thread.
I was suspecting that my Dagger graph isn't properly managed (especially on providing Realm instance), thus whenever I make query for data, it gives me the error.
So, my Dagger component looks like this:
#Singleton
#Component(modules = {ContextModule.class, RepositoryModule.class, PresenterModule.class})
public interface AppComponent {
/* Inejct application */
void inject(FourdoApp fourdoApp);
/* Realm Helper */
void inject(DatabaseRealm databaseRealm);
/* Activity */
void inject(MainActivity mainActivity);
void inject(TaskDetailActivity taskDetailActivity);
/* Presenter*/
void inject(MainPresenter mainPresenter);
void inject(TaskDetailPresenter taskDetailPresenter);
/* Model repository*/
void inject(TaskRepositoryImpl taskRepository);
}
Inside RepositoryModule.class;
#Module
public class RepositoryModule {
#Provides
#Singleton
Repository<Task> provideTaskRepository() {
return new TaskRepositoryImpl();
}
// Here I provide DatabaseRealm class instance
#Provides
#Singleton
public DatabaseRealm provideDatabaseRealm() {
return new DatabaseRealm();
}
}
Not sure whether I did this correctly or not. You can view the source for DI here.
For the data request to happen, inside MainActivity, I injected MainPresenter and call onRequestData interface to request it from the Presenter. From there, Presenter will make the call to Repository for the said data.
public class MainActivity extends BaseActivity implements MainContract.View {
#Inject
MainPresenter mainPresenter;
// ...
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Injecting MainActivity class
Injector.getAppComponent().inject(this);
mainPresenter.attachView(this);
// Requesting for data from Presenter
mainPresenter.onRequestData();
}
// ...
#Override
public void onRequestDataSuccess(List<String> taskList) {
doAdapter.addAll(taskList);
doAdapter.notifyDataSetChanged();
}
}
Inside MainPresenter, I injected Repository interface to make request from TaskRepositoryImpl for the real data from database.
public class MainPresenter extends BasePresenter<MainContract.View> implements MainContract.Presenter {
#Inject
Repository<Task> taskRepository;
public MainPresenter() {
Injector.getAppComponent().inject(this);
}
#Override
public void onRequestData() {
requestData();
}
private void requestData() {
taskRepository.findAll()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.map(this::mapToStringList)
.subscribe(new Observer<List<String>>() {
#Override
public void onNext(List<String> strings) { // Error in this line
if (strings.size() > 0) {
mView.onRequestDataSuccess(strings);
} else {
mView.showEmpty();
}
}
});
}
}
Inside TaskRepositoryImpl, here is how I did the findAll and it should return data from DatabaseRealm:
#Override
public Observable<List<Task>> findAll() {
return Observable.create(subscriber -> {
try {
List<Task> models = databaseRealm.findAll(Task.class);
subscriber.onNext(models);
subscriber.onComplete();
} catch (Exception e) {
subscriber.onError(e);
}
});
}
Code for DatabaseRealm are as follows:
public class DatabaseRealm {
#Inject
Context context;
RealmConfiguration realmConfiguration;
public DatabaseRealm() {
Injector.getAppComponent().inject(this);
}
public void setup() {
if (realmConfiguration == null) {
Realm.init(context);
realmConfiguration = new RealmConfiguration.Builder()
.deleteRealmIfMigrationNeeded()
.build();
Realm.setDefaultConfiguration(realmConfiguration);
} else {
throw new IllegalStateException("Realm already configured");
}
}
public Realm getRealmInstance() {
return Realm.getDefaultInstance();
}
public <T extends RealmObject> T add(T model) {
Realm realm = getRealmInstance();
realm.beginTransaction();
realm.copyToRealm(model);
realm.commitTransaction();
return model;
}
public <T extends RealmObject> T update(T model) {
Realm realm = getRealmInstance();
realm.beginTransaction();
realm.copyToRealmOrUpdate(model);
realm.commitTransaction();
return model;
}
public <T extends RealmObject> T remove(T model) {
Realm realm = getRealmInstance();
realm.beginTransaction();
realm.copyToRealm(model);
realm.deleteAll();
realm.commitTransaction();
return model;
}
public <T extends RealmObject> List<T> findAll(Class<T> clazz) {
return getRealmInstance().where(clazz).findAll();
}
public void close() {
getRealmInstance().close();
}
}
Full source code for this flawed code is right here.
I'd like to make it clear that I have limited knowledge on Realm instances being used in Dagger.
I followed this tutorial for the Repository Design Pattern with Realm, but it doesn't include Dagger for its dependency injection.
Can someone guide me on why it is always telling I'm calling Realm from incorrect thread?
I think you get this error because of this:
#Override
public Observable<List<Task>> findAll() {
return Observable.create(subscriber -> {
try {
List<Task> models = databaseRealm.findAll(Task.class);
subscriber.onNext(models);
subscriber.onComplete();
} catch (Exception e) {
subscriber.onError(e);
}
});
}
You are subscribing to io Thread but you inject your databaseRealm in Main Thread.
if you get instance in your observable's create you'll not get this error.
#Override
public Observable<List<Task>> findAll() {
return Observable.create(subscriber -> {
try {
Realm realm = getRealmInstance();
List<Task> models = realm.findAll(Task.class);
subscriber.onNext(models);
subscriber.onComplete();
} catch (Exception e) {
subscriber.onError(e);
}
});
}
You need to setup RealmConfigration only once in the Application class and use the Realm.getDeafultInstance() method to access Realm Database
With Dagger you need to Pass only realm instance in constructor
You can follow this Example and fork it
Its not exactly the same code you posted here.But it might help to understand dagger better with MVP ,RxJava and Realm

Binding to singleton property

Is there a way to bind property of the VM to any kind of singleton property ( static resource property, property in the singleton service... ) in a way that we don't need to use IMessenger or to handle SingletonServiceResolved OnPropertyChanged?
It feels kind of dirty for me (even if it is in the base class) to have each activity to handle changes in my singleton Clock Property.
public class ClockService : ObservableObject, IClockService {
private DateTime _clock;
public DateTime Clock {
get{ return _clock;}
set { _clock = value; RaisePropertyChanged("Clock"); }
}
}
public class SomeViewModel : BaseViewModel {
private IClockService _clockService;
private IMvxMessenger _messenger;
public SomeViewModel(IClockService clockService, IMvxMessenger messenger) {
_clockService=clockService;
_messenger = messenger;
//trying to avoid
clockService.PropertyChanged += OnClockServicePropertyChanged;
}
public DateTime MyClock {
get{return _clockService.Clock;}
}
private OnClockServicePropertyChanged(...) {
if(e.PropertyName=="Clock") RaisePropertyChanged("Clock");
}
}
One way to bind to a singleton is to expose the singleton via a property on your ViewModel:
public Thing MySingleton
{
get
{
return Thing.Instance;
}
}
Update after comment that the singleton isn't constant:
You could use another constant singleton as a holder - and this can then implement INotifyPropertyChanged - e.g.:
public class ThingHolder : MvxNotifyPropertyChanged
{
public static readonly ThingHolder Instance = new ThingHolder();
private Thing _thing;
public Thing CurrentThing
{
get { return _thing; }
set { _thing = value; RaisePropertyChanged(() => CurrentThing); }
}
}
Your VMs can then properties like:
public ThingHolder ThingHolder
{
get
{
return ThingHolder.Instance;
}
}
Your views can then bind to expressions like ThingHolder.CurrentThing

Roboguice 2.0 (android): POJO injection error (always null)

My base POJO class:
public class BaseDao {
public BaseDao() {
}
// ...
}
My extends POJO class:
public class KelvinDao extends BaseDao {
public KelvinDao () {
super();
}
// ...
}
I want to use KelvinDao in a service like that:
public class HanKelvinHandler extends HttpRequestHandler {
#Inject
private KelvinDao mKelvinDao;
public void treatGet() {
mKelvinDao.blabla(); !!! mKelvinDao is always NULL
}
It's really simple but it doesn't work :(
Thank you guys for your help!
How are you creating HanKelvinHandler? If you're doing it within a subclass of a RoboGuice class, such as RoboActivity, then it should just work. Example:
public class MyActivity extends RoboActivity
{
#Inject
private HanKelvinHandler m_handler;
[...]
}
Otherwise (i.e., you're creating it within another POJO), you're in regular Guice land, and I believe you will need to use the injector to get an instance of it. Example:
public class MyClass
{
public void doSomething()
{
Injector injector = Guice.createInjector( new YourGuiceBindings() );
HanKelvinHandler handler = injector.getInstance( HanKelvinHandler.class );
handler.treatGet(); // mKelvinDao should be good now
}
}
If you haven't seen the use of the injector before, or you don't know what to put for YourGuiceBindings(), then you may need to read the following:
https://github.com/roboguice/roboguice/wiki/Simple-Custom-Binding
https://code.google.com/p/google-guice/wiki/GettingStarted
It seems like there should be a way to do this without using the injector, but I don't know.

Categories

Resources