Parse different objects in onChanged() callback, Livedata - android

I want to listen to value changes in different fields in an activity.
So I have implemented the Observer interface, and bind these fields. How do I differentiate which value is changed in onChanged() since there is one callback for both variables or what is the best practice/efficient way (in terms of memory consumption) to parse incoming objects in onChanged()?
public abstract class BaseActivity extends AppCompatActivity implements Observer {
public MutableLiveData<Integer> status;
public MutableLiveData<UserAccount> userAccount;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
status.observe(this, this);
userAccount.observe(this, this);
}
#Override
public void onChanged(#NonNull Object o) {
// Best practice to parse/know different object types?
}
}

You should avoid implementing in this manner since the types (Integer,UserAccount) for the Observer is different.
Since there is not status object indicating which MutableLiveData obj received the value, one work around is to use instanceOf operator in onChanged and check if its Integer or UserAccount.
I would recommend you to avoid this since it would not be a great design and difficult to maintain.
You can make it separation of concern by creating two Observers one with Observer<Integer> and another with Observer<UserAccount> as follows:
class StatusObserver implements Observer<Integer> {
#Override
public void onChanged(#NonNull Integer status) {
// do something
}
}
class UserAccountObserver implements Observer<UserAccount> {
#Override
public void onChanged(#NonNull UserAccount userAccount) {
// do something
}
}
This is much cleaner approach, easy to maintain and since you have separated it, changes to handling of status won't impact handling of userAccount.
Alternatively, you can implement them as anonymous classes as follows:
status.observe(this, new Observer<Integer>() {
#Override
public void onChanged(#Nullable final Integer integer) {
}
});

Related

Am I allowed to observe a ViewModel, if I clean up the back references?

The suggested way to implement ViewModel is to expose the changing data by using LiveData objects to activities, fragments and views. There are cases, when LiveData is not an ideal answer or no answer at all.
The natural alternative would be, to apply the observer pattern to the ViewModel, make it an observable. When registering observers to the ViewModel, the ViewModel will hold callback references to notify the observers.
The documentation says, a ViewModel must not hold references to activities, fragments or views. The only answer to the question "why" I found is, that this may cause memory leaks. Then how about cleaning up the references to avoid memory leaks?
For views this is a difficulty. There is no defined moment, when the view goes away. But activities and fragments have a defined lifecycle. So there are places to unregister as observers.
What do you think? Is it valid to register activities as observers to ViewModels if you take care to always unregister them? Did you hit upon any valid information about this question?
I set a small reward for the best answer. It's not because I think it a recommended solution (as it does not work with views). I just want to know and extend my options.
public class ExampleViewModel extends ViewModel {
public interface OnEndListener {
public void onEnd();
}
private List<OnEndListener> onEndListeners = new ArrayList<>();
public void setOnEndListener(OnEndListener onEndListener) {
onEndListeners.add(onEndListener);
}
public void removeOnEndListener(OnEndListener onEndListener) {
onEndListeners.remove(onEndListener);
}
public void somethingHappens() {
for (OnEndListener onEndListener: new ArrayList<OnEndListener>(onEndListeners) ) {
onEndListener.onEnd();
}
}
}
public class ExampleActivity extends AppCompatActivity {
ExampleViewModel exampleViewModel;
ExampleViewModel.OnEndListener onEndListener;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
onEndListener = new ExampleViewModel.OnEndListener() {
#Override
public void onEnd() {
finish();
}
};
exampleViewModel = ViewModelProviders.of(this).get(ExampleViewModel.class);
exampleViewModel.setOnEndListener(onEndListener);
}
#Override
protected void onDestroy() {
super.onDestroy();
exampleViewModel.removeOnEndListener(onEndListener);
}
}
To ask "am I allowed..." is not really a useful question, IMO. The docs are clear that what you are suggesting is discouraged and why. That said, I expect that your code would probably work as expected and is therefore "allowed" (i.e. not prevented by a technical constraint).
One possible gotcha scenario: InstanceA of ExampleActivity is started and kicks off some long-running task on the ExampleViewModel. Then, before the task completes, the device is rotated and InstanceA is destroyed because of the configuration change. Then, in between the time when InstanceA is destroyed and a new InstanceB is created, the long-running task completes and your view model calls onEndListener.onEnd(). Except: Oh no! The onEndListener is null because it was cleared when InstanceA was destroyed and hasn't yet been set by InstanceB: NullPointerException
ViewModel was designed (in part) precisely to handle edge cases like the gotcha scenario above. So instead of working against the intended use of the ViewModel, why not just use the tools it offers along with LiveData to accomplish the same thing? (And with less code, I might add.)
public class ExampleActivity extends AppCompatActivity {
ExampleViewModel exampleViewModel;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
exampleViewModel = ViewModelProviders.of(this).get(ExampleViewModel.class);
exampleViewModel.getOnEndLive().observe(this, new Observer<Boolean>() {
#Override
public void onChanged(#Nullable Boolean onEnd) {
if (onEnd != null && onEnd) {
finish();
}
}
});
}
}
public class ExampleViewModel extends ViewModel {
private MutableLiveData<Boolean> onEndLive = new MutableLiveData<>();
public MutableLiveData<Boolean> getOnEndLive() {
return onEndLive;
}
public void somethingHappens() {
onEndLive.setValue(true);
}
}
Think of the LiveData in this case not as actual "data" per se, but as a signal that you can pass from your ViewModel to your Activity. I use this pattern all the time.

Android manage multi request rxJava on rotation device

I'm using MVVM on android application and i want to manage requests and rxJava on device rotation, how can i disable request after rotation device and countinue from last request?
this is my simple code to know how can i doing that, but i can't find any document and sample code about it
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_register);
...
Observer<String> myObserver = new Observer<String>() {
#Override
public void onError(Throwable e) {
// Called when the observable encounters an error
}
#Override
public void onComplete() {
}
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(String s) {
// Called each time the observable emits data
Log.e("MY OBSERVER", s);
}
};
Observable.just("Hello").subscribe(myObserver);
}
I'm using latest version of rxJava
Handling rotation is a cool challenge in Android. There're a few ways to do that.
1- Services: You can use a service and handle your network requests or other background operations in service. Also with Services, you'll seperate your business logic from ui.
2- Worker Fragment: Worker fragment is a fragment instance without a layout. You should set your worker fragment's retainInstanceState to true. So you'll save your fragment from orientation change and will not lose your background operations.
Why Worker Fragment? If you set retainInstanceState true to a fragment with layout, you'll leak views.
If you're using MVVM you can implement ViewModel as a Worker Fragment which as setRetainInstanceState = true
3- Global Singleton Data Source: You can create a global singleton data source class which handles your operations in an independent scope from Activity / Fragment lifecycle in your application.
4- Loaders: Loaders can recover state from orientation changes. You handle your operations with loaders but they are designed to load data from disk and are not well suited for long-running network requests.
Extra: You can use Path's Priority Job Queue to persist your jobs:
https://github.com/path/android-priority-jobqueue
Edit: You can check my repo for handling device rotation without using Google's new architecture components. (As an example of Worker Fragment which i pointed in my answer.)
https://github.com/savepopulation/bulk-action
You have the following options:
Use some global Singleton, or your Application class, that holds your logic, not within your Activity's lifecycle
Use a Service that runs next to your activity/application
Use a Loader
Global state is often bad and makes your code hard to test / debug. Services tend to be overkill.
For your use case of device rotation and continuing where one left off you'd usually use a Loader, which keeps running on rotation and only gets destroyed once you leave the activity.
I also recently wrote an article about one possible solution to use Loaders together with RxJava to keep state during orientation changes.
You can take advantage of Fragment#setRetainInstance(true). With that flag set, fragment is not destroyed after device rotation and can be used as an object container. Please look at this sample which also stores Observable - https://github.com/krpiotrek/RetainFragmentSample
you need to override
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
When device is rotated store data in bundle then inside on create check
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState == null){
//saved instance is null
}else{
//get your stored values here
counter = savedInstanceState.getInt("value",0); //here zero is the default value
}
}
How I'm doing this is to have a singleton class (or any long living Object as explained by savepopulation earlier, but - the trick is to store the loaded data in a BehaviorSubject, and subscribe to that subject in the Activity instead of the original network request.
This way:
public class MyNetworkSingleton {
// This static service survives orientation changes
public static MyNetworkSingleton INSTANCE = new MyNetworkSingleton();
private final BehaviorSubject<String> dataSubject = BehaviorSubject.create();
public Observable<String> getData() {
if (!dataSubject.hasValue()) {
refreshData(); // No data is loaded yet, load initial data from network
}
return dataSubject;
}
public void refreshData() {
someDataSourceCall().subscribe(new Observer<String>() {
#Override
public void onError(Throwable e) {
// Remember, this point also needs error handling of some form,
// e.g. propagating the error to the UI as a Toast
}
#Override
public void onComplete() {
}
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(String data) {
dataSubject.onNext(data); // this refreshes the internally stored data
}
});
}
private Observable<String> someDataSourceCall() {
return // some network request here etc. where you get your data from
}
}
and then:
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
Observer<String> myObserver = new Observer<String>() {
#Override
public void onError(Throwable e) {
// Called when the observable encounters an error
}
#Override
public void onComplete() {
}
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(String s) {
// Called each time the observable emits data
Log.e("MY OBSERVER", s);
}
};
MyNetworkSingleton.INSTANCE.getData().subscribe(myObserver);
myRefreshButton.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
// refresh data from network only when button is pressed
MyNetworkSingleton.INSTANCE.refreshData();
}
});
}
This way only first time you need the data from network it will be loaded, or when the user clicks a refresh button (myRefreshButton).

how to be notified in view in Databinding library?

Hi my problem is that my activity listening for viewmodel field changes but callback not get called!
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
binding.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
#Override
public void onPropertyChanged(Observable observable, int i) {
Log.d(getClass().getSimpleName(), "changed");
}
});
User user = new User("user");
binding.setUser(user);
user.setName("newUser");
}
}
and my viewModel:
public class User extends BaseObservable {
public String name;
public User(String name) {
this.name = name;
}
#Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
}
UI updated but callback not get called. I want a callback plus UI update.
I want to know data binding library works like this! there is Libraries like Rxjava for this but I don't like to import it.
When you do this:
ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
binding.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
#Override
public void onPropertyChanged(Observable observable, int i) {
Log.d(getClass().getSimpleName(), "changed");
}
});
User user = new User("user");
binding.setUser(user);
user.setName("newUser");
You should receive one notification on the callback you provided and it should be received when you call binding.setUser(user). Look for ActivityMainBinding.java and you will see the code generated for the class in your app's build folder. In the setUser() method, you'll see the call to notifyPropertyChanged(BR.user) that will call your listener.
The problem you're seeing is that the data change you want (name) is happening on the User and not on the Binding. In order to get notifications on changes to the user, you must add a lister like this:
ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
User user = new User("user");
user.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
#Override
public void onPropertyChanged(Observable observable, int i) {
Log.d(getClass().getSimpleName(), "changed");
}
});
binding.setUser(user);
user.setName("newUser");
Anything implementing the Observable interface will support any number of listeners, so you can feel free to listen for events.
Are you expecting to get the callback when you called setUser ? Because that's not the way it works. setUser explicitly sets the data model views on the bound views. The property change callback would be invoked if you change the value of any of the data model properties beyond this point. Change your code to this and i guess you would get the callback -
User user = new User("sa");
binding.setUser(user);
user.setName("Johny Depp"); //you should receive the callback beyond this and the UI would update too
Edit -
A class implementing the Observable interface will allow the binding to attach a single listener to a bound object to listen for changes of all properties on that object.
The Observable interface has a mechanism to add and remove listeners, but notifying is up to the developer. To make development easier, a base class, BaseObservable, was created to implement the listener registration mechanism. The data class implementer is still responsible for notifying when the properties change. This is done by assigning a Bindable annotation to the getter and notifying in the setter.

how to listen for SQLite database changes in android

I'm using SQLite database in android and want to listening for any database changes. How can I do this?
Thanks for all future help!
In fact, SQLite offers such functionality: SQLite Data Change Notification Callbacks
How it can be used in Android is another story though..
SQLite doesn't offer any change listener functionality; you have to monitor it yourself. The simplest way to achieve this would be to send a Broadcast (or even better, a LocalBroadcast) anytime you modify the database. Some of the database libraries already offer functionality that is similar to this - check out GreenDAO.
a simple implementation of changeListener for the database on Android
suppose that you have a class to handle your queries in your android app, we need to make the database methods observable.
and also we need some listeners to observe the abovementioned observable. let's make the database handler observable:
let's make the observable interface:
public interface DatabaseObservable {
//register the observer with this method
void registerDbObserver(DatabaseObserver databaseObserver);
//unregister the observer with this method
void removeDbObserver(DatabaseObserver databaseObserver);
//call this method upon database change
void notifyDbChanged();
}
now implement the observable in your database class
public class LocalStorageDb extends SQLiteOpenHelper implements DatabaseObservable {
LocalStorageDb lDb;
//make it Singleton
public static synchronized LocalStorageDB getInstance(Context context) {
if (mlLocalQuickChatDB == null) {
mlLocalQuickChatDB = new LocalStorageDB(context.getApplicationContext());
}
return mlLocalQuickChatDB;
}
//there are some methods to do some queries
public void createContact(Foo foo, Bar bar){
//some queries here
//call the Observable Method to let know the observers that it has changed
onDatabaseChanged();
}
//now override the DatabaseObservable method which is responsible to notify the listeners
#Override
public void onDatabaseChanged() {
for (DatabaseObserver databaseObserver:observerArrayList){
if (databaseObserver!= null){
databaseObserver.onDatabaseChanged();
}}
}
//also you need functions to **register** or **unregister** the observers:
#Override
public void registerDbObserver(DatabaseObserver databaseObserver) {
if (!observerArrayList.contains(databaseObserver)){
observerArrayList.add(databaseObserver);
}
#Override
public void removeDbObserver(DatabaseObserver databaseObserver) {
observerArrayList.remove(databaseObserver);
}
then we need an observer to observe the changes:
public interface DatabaseObserver {
void onDatabaseChanged();
}
now in your activity or fragment, there is a function to fetch the changes, like getLocalContact. implement the observer on the fragment for example:
public class ExampleFragment extends Fragment implements DatabaseObserver {
LocalStorageDB localStorageDB;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
localStorageDB = LocalStorageDB.getInstance();
}
#Override
public void onPause() {
super.onPause();
localStorageDB.removeObserver(this);
}
#Override
public void onResume() {
localStorageDB.registerObserver(this);
super.onResume();
}
public ExampleFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_example, container, false);
}
#Override
public void onDatabaseChanged() {
getLocalContact();
}
private void getLocalContact(){
//function to fetch contacts from database
}
}
Hi I'd like to add a new suggestion Incase of firebase usage. You can make a new json node for users messages and use On Data Change to detect the new number of unread messages then update your ui in the onDatachange. Is that smart or far away from the main idea?

How to notify an activity when GlobalVariables are changed

I have an android application that is connected to the computer via USB cable. I use a TCPServer Class to send messages and listen. For example:
When I send a message like: request:x
I get the response: response:x:55
I need to make changes on my activity according to the response I get. At the moment I temporarily solved the problem by passing activity and activity class object to the TCPServer's constructor
public TCPServer(int portNum, Activity activity, IntroActivity ia) {
super();
port = portNum;
this.activity = activity;
this.ia = ia;
}
Then after I receive the response:
void updateButton(final int color, final String txt) {
activity.runOnUiThread(new Runnable() {
public void run() {
ia.getConnectionButton().setBackgroundColor(color);
ia.getConnectionButton().setText(txt);
}
});
}
As you see, this is not effective at all. I need to somehow notify the activity whenever a relevant variable is received. I use a Class for GlobalVariables and change those static variables after listen(), however I am having troubles notifying the activity.
First of all, it is almost always bad practice to pass Activity instances around. This is a time when it's bad.
Define an interface and use a callback to let the activity know that a response has been received.
public interface ResponseReceivedListener {
void onResponseReceived(int arg1, string arg2); // <- add arguments you want to pass back
}
In your TCPServer class:
ArrayList<ResponseReceivedListener> listeners = new ArrayList<>();
// ...
public void setResponseReceivedListener(ResponseReceivedListener listener) {
if (!listeners.contains(listener) {
listeners.add(listener);
}
}
public void removeResponseReceivedListener(ResponseReceivedListener listener) {
if (listeners.contains(listener) {
listeners.remove(listener);
}
}
When you receive a response:
for (ResponseReceivedListener listener : listeners) {
listener.onResponseReceived(arg1, arg2);
}
In your Activity:
public class MainActivity extends Activity implements ResponseReceivedListener {
// ...
#Override
public void onCreate(Bundle savedInstanceState) {
// ...
tcpServer.setResponseReceivedListener(this);
// ...
}
public void onResponseReceived(int arg1, string arg2) {
// do whatever you need to do
}
// ...
}
All from memory so please excuse typos.
This approach decouples the classes. The TCP Server has no knowledge of the activities. It simply calls back to any listeners registered. Those listeners might be Activities, they might be services. They might be instances of MySparklyUnicorn. The server neither knows nor cares. It simply says "if anyone's interested, I've received a response and here are the details".

Categories

Resources