I noticed that MvxMessenger subscriptions were being invoked multiple times due to multiple instances of the same ViewModel. I read a bit about unsubscribing and disposing the tokens (which works and prevents multiple invocations) but I wanted to see the ViewModel being garbage collected naturally and the Messenger subscription along with it.
I wanted to set up a test project for Android similar to this one https://github.com/slodge/MessengerHacking. So here are the two ViewModels
public class FirstViewModel : MvxViewModel
{
private string _hello = "Hello MvvmCross";
public string Hello
{
get { return _hello; }
set { _hello = value; RaisePropertyChanged(() => Hello); }
}
private MvxCommand _showSecond;
public ICommand ShowSecond {
get {
_showSecond = _showSecond ?? new MvxCommand(() => ShowViewModel<SecondViewModel> ());
return _showSecond;
}
}
}
And
public class SecondViewModel : MvxViewModel
{
private readonly IMvxMessenger _messenger;
private readonly MvxSubscriptionToken _token;
public SecondViewModel(IMvxMessenger messenger) {
_messenger = messenger;
_token = _messenger.Subscribe<MyMessage> ((message) => {
Debug.WriteLine("incoming message");
});
}
private MvxCommand _send;
public ICommand Send {
get {
_send = _send ?? new MvxCommand(() => _messenger.Publish (new MyMessage (this)));
return _send;
}
}
private MvxCommand _garbageCollect;
public ICommand GarbageCollect {
get {
_garbageCollect = _garbageCollect ?? new MvxCommand(() => GC.Collect ());
return _garbageCollect;
}
}
}
Then I just have two MvxActivities bound to these ViewModels. If I go to SecondViewModel and click send I see the subscribed event fire off once. If I go back and forth between the First and SecondViewModel these event subscriptions build up and clicking Send fires each of them. Clicking GarbageCollect doesn't seem to make any difference (I'm hoping to see it invoked only once after clicking this).
It feels as though when I click the back button from SecondViewModel, once the MvxActivity is destroyed then SecondViewModel should be eligible for garbage collection.
Another thing I noticed is that even if I Subscribe without saving it into a token, the behaviour is the same. The only way I have successfully got the events to stop firing is by saving the token and calling Unsubscribe or Dispose on the token, however it feels like SecondViewModel is still not getting garbage collected in this case.
Could this be something to do with the recent changes to Xamarin.Android? Or is there something I just don't get!
Many thanks
I know it is too late for answer, but for the sake of reference:
Short Answer:
In view(MvxActivity), handle DestroyCalled event like this:
DestroyCalled += (s, e) =>
{
if (ViewModel is IDisposable)
(ViewModel as IDisposable).Dispose();
};
In viewmodel, implement IDisposable interface:
public new void Dispose()
{
base.Dispose();
//Unsubscribe messages here
}
Long Answer:
http://slodge.blogspot.co.uk/2013/11/n42-is-my-viewmodel-visible-can-i-kill.html
Related
I'm fairly new to Flutter and am now implementing Riverpod. I've studied a number of guides and tutorials, and they all seem to start with the same simple example - either a basic provider which provides "Hello World", or an integer, as follows:
final countProvider = Provider<Int>((ref) => 0);
final hellowWorldProvider = Provider<String>((ref) => "Hello world");
There is then a Consumer object within a StatelessWidget which calls watch() this provider to get the value.
My question is, what is the point of this? Not a single one of the tutorials that I've found show a way of modifying the value returned by the provider, so it will always be the same. What advantage is there in using the provider here? Why call watch() on a value that doesn't change? You could just hard code the value in the Widget, or if that's frowned upon, use a constants file or equivalent.
Please tell me what I'm missing here, thanks!
Here are 2 use cases.
1. Knowing if a user is logged out
A simple use-case of a basic Provider is knowing if the user is logged in without depending on a userProvider directly.
Take a look at this model and provider:
class UserModel {
final String fullName;
final String email;
UserModel(this.fullName, this.email);
}
final userProvider = StateProvider<UserModel?>((ref) {
return UserModel("Person User", "person#stackoverflow.com");
});
final isLoggedInProvider = Provider<bool>((ref) {
bool isLoggedIn = ref.watch(userProvider) != null;
return isLoggedIn;
});
Say we have a logout method that sets the userProvider state value to null:
void logout(WidgetRef ref) {
ref.read(userProvider.state).state = null;
}
Immediately log out is called, every consumer listening to isLoggedInProvider would react to the "logged out" state of the user.
2. Dependency Injection
Say we have a basic Provider that provides a repository like so:
final dataRepositoryProvider = Provider<DataRepository>((ref) {
return DataRepository();
});
class DataRepository {
List<String> _data = [];
List<String> getSortedData() {
_data.sort();
return _data;
}
}
And there's a StateNotifierProvider that depends on DataRepository - If we don't want to keep creating new objects of DataRepository, it could be injected like so:
final dataStateNotifierProvider = StateNotifierProvider<DataStateNotifier, List<String>>((ref) {
var repo = ref.read(dataRepositoryProvider);
return DataStateNotifier(repo);
});
class DataStateNotifier extends StateNotifier<List<String>> {
final DataRepository _dataRepository;
DataStateNotifier(this._dataRepository) : super([]);
void getData(){
state = _dataRepository.getSortedData();
}
}
I hope this helps you understand.
Would like to have your help on my weird problem that currently I am facing. I tried for couple of days but no luck and finally decided to post here to take help.
I created a Snapshot Listener attached to a Collection in Firebase defined as follows :-
public class FirebaseTypingStatusLiveData extends LiveData<List<documentSnapshot>> {
// Logging constant
private static final String TAG = "FirebaseQueryLiveData";
// Document Reference
private final DocumentReference documentReference;
// Listener
private final MyDocumentListener listener = new MyDocumentListener();
// Handler
private final Handler handler = new Handler();
private ListenerRegistration listenerRegistration;
// Flag to remove listener
private boolean listenerRemovePending = false;
private MutableLiveData <List<documentSnapshot> mutableLiveData = new MutableLiveData<>();
// Constructor
public FirebaseTypingStatusLiveData(DocumentReference documentReference) {
this.documentReference = documentReference;
}
public LiveData<List<documentSnapshot>> checknow(){
// Add listener
if (!Listeners.LIVESAMPLE.containsKey(documentReference)) {
listenerRegistration = documentReference.addSnapshotListener(listener);
Listeners.LIVESAMPLE.put(documentReference, listenerRegistration);
} else {
listenerRegistration = Listeners.LIVETYPINGSTATUSSAMPLE.get(documentReference);
}
return mutableLiveData;
}
// Listener definition
private class MyDocumentListener implements EventListener<DocumentSnapshot> {
#Override
public void onEvent(#Nullable DocumentSnapshot documentSnapshot, #Nullable
FirebaseFirestoreException e) {
Log.d(TAG, "onEvent");
// Check for error
if (e != null) {
// Log
Log.d(TAG, "Can't listen to query snapshots: " + documentSnapshot
+ ":::" + e.getMessage());
return;
}
setValue(documentSnapshot);
mutableLiveData.setValue(documentSnapshot);
}
}
}
}
The snapshot reads the data perfectly and advised as and when data is available.
The snapshot data is getting displayed 1. in Fragment (not part of Activity that i am talking about) 2. Activity through two view models that have the same code as follows :
#NonNull
public LiveData<List<documentSnapshot>> getDataSnapshotLiveData() {
Firestore_dB db = new Firestore_dB();
DocumentReference docref = db.get_document_firestore("Sample/"+docID);
FirebaseTypingStatusLiveData firebaseTypingStatusLiveData = new
FirebaseTypingStatusLiveData(docref);
return firebaseTypingStatusLiveData.checknow();
}
The Fragment & Activity code is also same except changing owner which are as follows :-
LiveData<List<documentSnapshot>> liveData = viewmodel.getDataSnapshotLiveData();
liveData.observe(this, new Observer<List<documentSnapshot>>() {
#Override
public void onChanged(DocumentReference docreef) {
String name = docreef.get("name");
stringname.setText(name); // The text is displaying either in Fragment or in Activity but not in both.
});
My problem is i need data in both i.e. Fragment & Activity whereas I am getting data either in Fragment or in Activity depending upon the code which I commented.
Kindly advise where I am making mistake. Thanks in Advance
Honestly, I am not sure that my answer wouldn't lead you away to the false way, but you can try.
My guess is that your problem could be somehow connected with ViewModel sharing.
There is a well-known task How to share Viewmodel between fragments.
But in your case, that can't help, because you have to share ViewModel between activities (now you have two separate ViewModels and that could be problem with Firestore EventListeners).
Technically you can share ViewModel between activities (I haven't try since usually I use Single activity pattern). For that as a owner parameter in ViewModelProvider constructor you can set instance of your custom Application class (but you have implement interface ViewModelStoreOwner for it). After that both in your activity and in your fragment you can get the same ViewModel with the Application class-instance:
val sharedViewModel = ViewModelProvider(mainApplication, viewModelFactory).get(SharedViewModel::class.java)
I made LiveData static that listens to changes in source data and provide updated content were ever required in different Activity.
I have a ViewPager with two pages namely Popular and All. What I'm trying to achieve is only push items that have popular tag true to Popular whereas push all items to All.
Currently I have a single class which is used in the PagerAdapter and passing in the page type. How do I filter out PublishSubject so that each page only displays necessary items accordingly.
Both my Observer are subscribed to a single PublishSubject, but I
want to filter when emitting.
Please comment if the question is unclear. I'll try my best to relay this problem. Also sorry if it has already been answered since I couldn't find anything relevant.
The code I'm using is this based on this architecture in which I have a Firebase data store FirebaseSubscriptionDataStore which provides the PublishSubject. This is later subscribed to by SubscribeToSubscriptionUpdates in SubscriptionListPresenterImpl
Thanks in advance.
You can basically define two different methods to get Observable (or Flowable) from PublishSubject. First observable will emit all of the items and second one only popular ones:
public class DataStore {
private PublishSubject<DataItem> dataItemPublishSubject = PublishSubject.create();
public Flowable<DataItem> getAllObservable() {
return dataItemPublishSubject.toFlowable(BackpressureStrategy.BUFFER);
}
public Flowable<DataItem> getPopularObservable() {
return dataItemPublishSubject.toFlowable(BackpressureStrategy.BUFFER)
.filter(new Predicate<DataItem>() {
#Override
public boolean test(DataItem dataItem) throws Exception {
return dataItem.popular;
}
});
}
public static class DataItem {
public final boolean popular;
public DataItem(boolean popular) {
this.popular = popular;
}
}
}
In case you don't want to two methods, you can move .filter() operator everywhere within you Rx chain and you might end up with something like this:
dataStore.getAllObservable()
.doOnNext(new Consumer<DataStore.DataItem>() {
#Override
public void accept(DataStore.DataItem dataItem) throws Exception {
pagerAdapter.addDataAll(dataItem);
}
})
.filter(new Predicate<DataStore.DataItem>() {
#Override
public boolean test(DataStore.DataItem dataItem) throws Exception {
return dataItem.popular;
}
})
.doOnNext(new Consumer<DataStore.DataItem>() {
#Override
public void accept(DataStore.DataItem dataItem) throws Exception {
pagerAdapter.addDataPopular(dataItem);
}
})
.subscribe();
I was wondering about token authentication with Retrofit/RxJava.
I was refactoring my code to use a DataManager, such that the activity evokes a method in the presenter, the presenter subscribes to the datamanager.getTrips which is then responsible for the call to the api.
I want to make sure that my accesstoken is valid, and if it is not generate it first and then complete the task. Would doOnCompleted be a good way of achieving this or is there a better way?
/*In DataManager, service is the REST Interface*/
public Observable<VtTripResponseModel> getTrips(final String start, final String end, final String time, final String date){
if(accessToken.isValid()){
return service.getTrip(accessToken.getAccessString(),start,end,time,date,JSON_PARAM);
}
else{
generateAccessToken().doOnCompleted(new Action0() {
#Override
public void call() {
getTrips(start, end, time, date);
}
});
}
/*What do I return here? Since this will not be reached*/
}
To elaborate on #david.mihola 's answear you could do it like this:
Observable.just(accessToken.isValid())
.flatMap(valid -> {
if(valid){
return Observable.just(accessToken);
} else {
return generateAccessToken();
})
.flatMap(token -> service.getTrip(token.getAccessString(),start,end,time,date,JSON_PARAM))
So that the first flatMap generates token if it is not valid and if it is, then simply passes it on(this assumes that generateToken() returns Observable<AccessToken>). Second flatMap is just the thing that you wanted to do.
And to give some context to #MatBos's elaboration on my comment, especially with regard to your question
What do I return here? Since this will not be reached
It felt quite eye-opening for me when I realized that an Observable (or at least a cold-one, like the one we are talking about here, and the one that #MatBos described in his answer) is essentially a description of a future computation.
Returning an Observable just means that you return a blue-print for what should happen if and when someone (i. e. the code that called your getTrips method) actually subscribes to that Observable. Of course, an Observable is also an asynchronous stream of events, but I think that my description here is valid, too.
So, what do you return? A description that says:
If someone subscribes
1. First check if we have valid access token
2. a) If we do, just forward the access token for later use
b) If we don't, generate a new one access token and forward that
3. Take whatever access token you get - it is now guaranteed to be valid and use to retrieve the trips.
4. Forward them to the subscriber when they are ready.
And that description is exactly the Observable that #MatBos described.
Thank you for the input, In the meantime I was flying away and found a similar, but formulated in another way post: Retrofit with RxJava which had an answer in it.
My code now looks like:
/*In DataManager*/
public Observable<VtTripResponseModel> getTrips(String start, String end, String time, String date){
return service.getTrip(accessToken.getAccessString(),start,end,time,date,JSON_PARAM);
}
public Observable<VtResponseModel> getLocationByInput(String input){
return service.getLocationByInput(accessToken.getAccessString(),input,JSON_PARAM);
}
/*SF 26201420*/
public <T> Func1<Throwable,? extends Observable<? extends T>> refreshTokenAndRetry(final Observable<T> toBeResumed) {
return new Func1<Throwable, Observable<? extends T>>() {
#Override
public Observable<? extends T> call(Throwable throwable) {
// Here check if the error thrown really is a 401
if (isHttp401(throwable)) {
return service.getAccessToken(CREDENTIALS, DEVICE).flatMap(new Func1<AccessToken, Observable<? extends T>>() {
#Override
public Observable<? extends T> call(AccessToken token) {
accessToken = token;
return toBeResumed;
}
});
}
// re-throw this error because it's not recoverable from here
return Observable.error(throwable);
}
};
}
And the method in my presenter now looks like
public void loadRepositories(String search){
subscription = manager.getLocationByInput(search)
.onErrorResumeNext(manager.refreshTokenAndRetry(Observable.defer(() -> manager.getLocationByInput(search))))
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(application.defaultSubscribeScheduler())
.subscribe(new Subscriber<VtResponseModel>() {... etc}
Now when the first call is made after starting the application, it will generate an accesstoken since I recieve a 401. Then that accesstoken is stored within the manager, and reused until there is a new 401 (after it has expired).
the RealmChangeListener is not working as I expected, here is what I do with it :
public ArrayList<NotificationMessage> getNotifications(RealmChangeListener<RealmResults<NotificationMessage>> listener) {
RealmResults<NotificationMessage> results = realm.where(NotificationMessage.class).findAll();
results.addChangeListener(listener);
return (ArrayList) realm.copyFromRealm(results);
}
public void addNotification(NotificationMessage notif) {
realm.beginTransaction();
realm.copyToRealm(notif);
realm.commitTransaction();
}
Basicly I'm using Realm to store gcm messages. My GCMListenerService calls addNotification() when he receives one, and I have a fragment that displays theses notifications which call getNotifications(this) when it's created. It implements RealmChangeListener<RealmResults<NotificationMessage>> :
#Override
public void onChange(RealmResults<NotificationMessage> element) {
RecyclerAdapter.refreshDataSet(Realm.getDefaultInstance().copyFromRealm(element));
}
My problem is that most of the time onChange() isn't called. Sometimes it is, sometimes several times in a row, and then it's not called again (whithout doing anything but refreshing my web page that sends the message to GCM). The notifications are well stored in database, if I call getNotifications again I'll see them all.
I started with realm this morning so I'd apprieciate if you have suggestion on how I should organise my "Realm" code.
public static <T extends RealmObject> ArrayList<T> convertRealmToPlainObject(RealmResults<T> results) {
ArrayList<T> returnedData = new ArrayList<>();
Iterator<T> it = results.iterator();
while (it.hasNext()) {
returnedData.add(it.next());
}
return returnedData;
}
From your description it is hard to give specific advice, but a few pointers:
Change listeners are only triggered on Looper events. So if you have multiple writes while handling an event. It will only trigger one onChange call.
Closing the Realm means that change listeners no longer will be triggered.
Changelisteners doesn't work on plain objects.