I am having the following code:
public class ViewModelDrivers extends ViewModel {
private MutableLiveData<List<DriverDetailsModel>> drivers;
public LiveData<List<DriverDetailsModel>> getDrivers() {
if (drivers == null) {
drivers = new MutableLiveData<>();
loadDrivers();
}
return drivers;
}
private void loadDrivers() {
CloudFactory.getInstance().getAllServerDrivers(PreferenceUtils.getInstance().getDeviceId(), new IServerAllTrucksCallback() {
#Override
public void onAllTrucks(List<DriverDetailsModel> d) {
drivers.setValue(d);
}
});
}
}
And the MainActivity on onCreate method:
ViewModelDrivers model = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).get(ViewModelDrivers.class);
model.getDrivers().observe(this, items -> {
//here never gets called. Why?
});
I didnt used ViewModelProviders since is deprecated. I never received a result in observe() method. Moreover drivers.setValue(d); gets set
Change drivers.setValue() to drivers.postValue( and if this helps, then you should change your code like Suraj Vaishnav defined, calling loadDrivers after subscribing to observer.
Maybe your drivers.setValue(d); called before return drivers;
So remove loadDrivers(); from getDrivers() method, like this:
public LiveData<List<DriverDetailsModel>> getDrivers() {
if (drivers == null) {
drivers = new MutableLiveData<>();
}
return drivers;
}
And call loadDrivers() from after setting observer, like this:
model.getDrivers().observe(this, items -> {
//here never gets called. Why?
});
loadDrivers(); //here call loadDrivers();
Related
In my ViewModel class I have a method that fetches data from the internet and sets them on LiveData via setValue() (I've used the approach from these docs):
public class PageViewModel extends ViewModel {
private MutableLiveData<List<?>> mDataList;
...
private LiveData<List<?> getDataList() {
if (mDataList == null) {
mDataList = new MutableLiveData<>();
}
Handler handler = new Handler(Looper.getMainLooper());
// getData() connects to the internet and fetches the online data
mInternetConnection.getData(new InternetConnection.ConnectionCallback<List<?>>() {
#Override
public void onComplete(Result<List<?>> result) {
if (result instanceof Result.Success) {
mDataList.setValue(((Result.Success<List<?>>) result).data);
} else {
Log.e(LOG_TAG, "ViewModel could not obtain data list");
}
}
}, handler);
return mDataList;
}
But mDataList.getValue() is null. The setValue() method is called on the main thread.
onComplete gets called for sure (checked).
When I check whether the value of mDataList is null in the onComplete method right after setValue(), the logs show that it is not null.
Why is it null and how should you get the value of the modified mDataList?
I've spent hours looking for a solution on the web, but couldn't find anything that could help. The related questions on this site don't help to solve the problem either.
EDIT:
From the docs:
In this example, the callback passed into the Repository's makeLoginRequest call is executed on the main thread. That means you can directly modify the UI from the callback or use LiveData.setValue() to communicate with the UI.
So I've followed that, but this part: "or use LiveData.setValue() to communicate with the UI" doesn't work.
The observer is set on it in the Fragment, but LiveData still don't get updated - list in onChanged is null, the screen remains blank, no errors.
Fragment code:
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentMainBinding.inflate(inflater, container, false);
View rootView = binding.getRoot();
binding.listRecyclv.setLayoutManager(new LinearLayoutManager(rootView.getContext()));
mAdapter = createAdapter(rootView.getContext(), pageViewModel.getList().getValue());
binding.listRecyclv.setAdapter(mAdapter);
pageViewModel.getList().observe(getViewLifecycleOwner(), new Observer<List<?>>() {
#Override
public void onChanged(#Nullable List<?> list) {
mAdapter.setList((ArrayList<MyObj>) list);
binding.listRecyclv.setAdapter(mAdapter);
}
}
}
});
Getter in ViewModel:
public LiveData<List<?>> getList() {
return getDataList();
}
Code in Adapter:
public class AppAdapter extends RecyclerView.Adapter<AppAdapter.AppViewHolder> {
...
public void setList(ArrayList<MyObj> list) {
mAppList = list;
notifyDataSetChanged();
}
Thanks in advance.
mDataList is null because the call you're making to get data is asynchronous. This means that the function will return before your api call (and its callbacks) does.
Read more here https://www.bmc.com/blogs/asynchronous-programming/
To get the list you need to observe data changes.
So something like this:
From the activity:
Kotlin:
viewModel.getDataList().observe(this) { dataList ->
//TODO: Do something with the list
}
Java:
final Observer<String> listObserver = new Observer<List<>>() {
#Override
public void onChanged(#Nullable final List<> newName) {
//TODO: Use the list
}
};
viewModel.getDataList().observe(this, listObserver);
The logic inside the observer will be called every time the list changes.
I solved it this way:
Changed this line in Fragment's onCreate method
from:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
pageViewModel = new ViewModelProvider(this).get(PageViewModel.class);
to
pageViewModel = new ViewModelProvider(requireActivity()).get(PageViewModel.class);
Thanks to this answer
Changed the data type of getDataList() method in the PageViewModel class from LiveData to MutableLiveData (without it the above step doesn't help):
from:
public class PageViewModel extends ViewModel {
private MutableLiveData<List<?>> mDataList;
...
private LiveData<List<?> getDataList() {
to:
private MutableLiveData<List<?> getDataList() {
and now it works! The observer gets triggered now and the data are updated.
How can I have my viewmodel call a function in my activity or fragment without using a callback and where no data is actually sent. LiveData is used to send data from the viewmodel to the view but I have no data. I just want to notify the ui about something. Should this be done using RxJava or is that overkill?
LiveData is just fine, here is what I did recently (derived from https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150)
first create a class
public class OneTimeEvent {
private Boolean received;
public OneTimeEvent() {
received = false;
}
public Boolean receive () {
if (!received) {
received = true;
return true;
}
return false;
}
}
then in your ViewModel expose your event
private MediatorLiveData<OneTimeEvent> eventListener = new MediatorLiveData<>();
public LiveData<OneTimeEvent> onEvent() {
return eventListener;
}
now you have to trigger the event somewhere in your ViewModel (like something else is finished)
eventListener.setValue(new OneTimeEvent()); //if its a background thread or callback use postValue!
that's it, now you can observe onEvent() in any activity or fragment you like
ViewModel.onEvent().observe(this, new Observer<OneTimeEvent>() {
#Override
public void onChanged(OneTimeEvent oneTimeEvent) {
if (oneTimeEvent.receive()){
// do something on event
}
}
});
Hope this helps, it acts just like an EventListener, only that you can Listen from Multiple Locations simultaneously, and each event will only be fired once, eg if the observer is reattached somewhere else.
I created the instance of View Model in onCreate method of an activity.
ticketViewModel = ViewModelProviders.of(this).get(TicketViewModel.class);
Then i have a method, AddTicket, which uses viewModel to hit a service and on response from viewModel i dismiss loading animation.
public void addTicket(View view){
ticketViewModel.AddTicket(id).observe(this, response ->{
dismissLoadingAnimation();
}
Now after adding a ticket, user can repress the Add Ticket button, and the addTicket() method will be called again.
but this time observer defined in ViewModel gets called 2 times, resulting in 2 network calls, and 2 dismissLoadingAnimation execution.
And if i keep pressing addTicket button, the number of executing observer defined inside ViewModel keep increases.
This is my View Model code.
public class TicketViewModel extends AndroidViewModel implements IServiceResponse {
MutableLiveData<String> mObservableResponse = new MutableLiveData<String>();
public MutableLiveData AddTicket(String id){
JsonObject jsonObject= new JsonObject();
jsonObject.addProperty("id", id);
NetworkUtility networkUtility= new NetworkUtility(this, ADD_TICKET);
networkUtility.hitService(URL, jsonObject, RequestMethods.POST);
return mObservableResponse;
}
#Override
public void onServiceResponse(String response, String callType){
if(serviceTag.equalsIgnoreCase(ADD_TICKET)){
mObservableResponse.setValue("success");
}
}
}
The number of executing observer defined inside ViewModel keep increases becasue with every click You're registering new observers. You're not supposed to register observer with onClick() method.
You should do it in onCreate() method of your Activity or in onViewCreated method of your fragment. If You'll do that, there won't be a need to removeObserver when You'll finish work. Lifecycle mechanism will cover it for you.
But if you really want answer for you question, this is how you can do it
yourViewModel.yourList.removeObservers(this)
Passing this means passing your Activity, or there is a second way:
yourViewModel.yourList.removeObserver(observer)
val observer = object : Observer<YourObject> {
override fun onChanged(t: YourObject?) {
//todo
}
}
The purpose of Viewmodel is to expose observables (Livedata)
The purpose of View(Activity/Fragment) is to get these observables and observe them
Whenever there is a change in these observables(Livedata) the change is automatically posted to the active subscribed owners(Activity/Fragment), so you need not remove them in onPause/onStop as it is not mandatory
I can suggest few changes to your code to solve the problem with the above mentioned pointers
ViewModel
public class TicketViewModel extends AndroidViewModel implements IServiceResponse {
MutableLiveData<String> mObservableResponse = new MutableLiveData<String>();
public LiveData<String> getResponseLiveData(){
return mObservableResponse;
}
public void AddTicket(String id){
JsonObject jsonObject= new JsonObject();
jsonObject.addProperty("id", id);
NetworkUtility networkUtility= new NetworkUtility(this, ADD_TICKET);
networkUtility.hitService(URL, jsonObject, RequestMethods.POST);
}
#Override
public void onServiceResponse(String response, String callType){
if(serviceTag.equalsIgnoreCase(ADD_TICKET)){
mObservableResponse.setValue("success");
}
}
}
View
onCreate(){
ticketViewModel = ViewModelProviders.of(this).get(TicketViewModel.class);
observeForResponse();
}
private void observeForResponse(){
ticketViewModel.getResponseLiveData().observe(this, response ->{
//do what has to be updated in UI
}
}
public void addTicket(View view){
ticketViewModel.AddTicket(id);
}
Hope this is of help :)
You only need to call the observe once, I prefer to do it in onResume and then call removeObserver in onPause:
Adds the given observer to the observers list
You keep adding listeners to the data so you get multiple callbacks.
Edit:
I took an existing code sample of mine for a Fragment and renamed everything (I hope), there's no example here for setting the data into the ViewModel but it should be ticketViewModel.AddTicket(id); in your case.
public class ListFragment extends Fragment {
private MyViewModel viewModel;
private MyRecyclerViewAdapter recyclerViewAdapter;
private Observer<List<DatabaseObject>> dataObserver;
private RecyclerView recyclerView;
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_layout, container, false);
initRecyclerView(rootView, getContext());
initObservers();
return rootView;
}
private void initRecyclerView(View rootView, Context context) {
recyclerViewAdapter = new MyRecyclerViewAdapter(context);
recyclerView = rootView.findViewById(R.id.recycler_view);
recyclerView.setAdapter(recyclerViewAdapter);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(context);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.addItemDecoration(new DividerNoLastItemDecoration());
}
private void initObservers() {
dataObserver = new Observer<List<DatabaseObject>>() {
#Override
public void onChanged(#Nullable final List<DatabaseObject> data) {
recyclerViewAdapter.setData(data);
}
};
}
#Override
public void onResume() {
super.onResume();
initViewModel();
}
private void initViewModel() {
FragmentActivity activity = getActivity();
if (activity != null) {
viewModel = ViewModelProviders.of(activity).get(MyViewModel.class);
viewModel.getData().observe(activity, dataObserver);
}
}
#Override
public void onPause() {
super.onPause();
if (viewModel != null) {
viewModel.getData().removeObserver(dataObserver);
viewModel = null;
}
}
}
I had similar problem. You could try to use SingleLiveEvent
Or, in my, more complicated case, i had to use custom observer. It would looks like this:
public class CustomObserver implements Observer<YourType> {
private MyViewModel mViewModel;
public CustomObserver (){}
public void setViewModel(MyViewModel model) {
mViewModel = model;
}
#Override
public void onChanged(#Nullable YourType object) {
mViewModel.AddTicket(id).removeObserver(this); // removing previous
mmViewModel.refreshTickets(); // refreshing Data/UI
// ... do the job here
// in your case it`s: dismissLoadingAnimation();
}
}
And using it like:
public void addTicket(View view){
ticketViewModel.AddTicket(id).observe(this, myCustomObserver);
}
If you are willing to do some changes, i think we can handle it in much cleaner way
LiveData is meant to be used to contain a property value of a view
In ViewModel
public class TicketViewModel extends AndroidViewModel implements IServiceResponse {
private MutableLiveData<Boolean> showLoadingAnimationLiveData = new MutableLiveData<String>();
public LiveData<Boolean> getShowLoadingAnimationLiveData(){
return showLoadingAnimationLiveData;
}
public void addTicket(String id){
JsonObject jsonObject= new JsonObject();
jsonObject.addProperty("id", id);
NetworkUtility networkUtility= new NetworkUtility(this, ADD_TICKET);
networkUtility.hitService(URL, jsonObject, RequestMethods.POST);
showLoadingAnimationLiveData.setValue(true);
}
#Override
public void onServiceResponse(String response, String callType){
if(serviceTag.equalsIgnoreCase(ADD_TICKET)){
showLoadingAnimationLiveData.setValue(false);
}
}
}
In 'onCreate' of your Activity/Fragment
ticketViewModel.getShowLoadingAnimationLiveData().observe(this,showLoadingAnimation->{
if(showLoadingAnimation != null && showLoadingAnimation){
startLoadingAnimation();
}else{
dismissLoadingAnimation();
}
})
The main concept is to divide the responsibilities,
Activity/Fragment doesn't need to know which process is going on, they only need to know what are the current properties/state of there child views.
We need to maintain a LiveData in ViewModels for each changing property/state depending on Views. ViewModel needs to handle the view states depending on whats happening.
Only responsibility the Activity/Fragment has about a process is to trigger it and forget and ViewModel needs handle everything(like informing Repositories to do the work and changing View Properties).
In your Case,
'addTicket' is a process about which Activity/Fragment doesn't need to know about there status.
The only responsibility of Activity/Fragment about that process is to trigger it.
ViewModel is one who needs to analyze the state of process(in-progress/success/failed) and give appropriate values to the LiveDatas to inform the respective Views
I understand there are a lot of information about it out there, but I haven't found one that matches my case yet.
I have a recycleview on a fragment that is always open, so the fragment basically never re-creates itself.
This is my code to load the adapter.
reLoad(); //method shown below
mRecycler.setAdapter(new SolicitationAdapter(myRealm.where(SolicitationDatabase.class).findAllAsync()));
And this is the logic I came up with:
public void reLoad() {
if (!myRealm.where(SolicitationDatabase.class).findAll().isEmpty()) {
mNothingHere.setVisibility(View.GONE);
mRecycler.setVisibility(View.VISIBLE);
} else {
mNothingHere.setVisibility(View.VISIBLE);
mRecycler.setVisibility(View.GONE);
}
}
It works great the first time the user opens the app.
The trouble starts when the user creates a record, since the fragment doesn't re-create itself it never reloads.
The reason I haven't been able to reload after user adds something is because the method to add a new record is on a singleton being called from a different activity. Which means when I try to do it I get a nullpointerexception when declaring the the recycleview and the textview.
Edit - What I tried (reloading views from another place)
I have a class called PostHelper, this class is in charge of posting a new record.
This is the constructor:
public PostHelper(Context context, Activity activity) {
this.mContext = context;
this.mActivity = activity; //I call this in order to use "findViewById"
This is where the post happens:
public String addSolicitation(File _file, boolean fromQueue) {
//loading view
TextView nothingHere = (TextView) mActivity.findViewById(R.id.nothing_here);
RecyclerView recycler = (RecyclerView) mActivity.findViewById(R.id.recycler);
...so on until after the post:
SolicitationAdapter n = new SolicitationAdapter(myRealm.where(SolicitationDatabase.class).findAll());
n.notifyDataSetChanged();
nothingHere.setVisibility(View.GONE);
recycler.setVisibility(View.VISIBLE);
And this is the stacktrace:
06-01 21:43:37.511 9122-9122/? E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.ga.realm3, PID: 9122
io.reactivex.exceptions.OnErrorNotImplementedException: Attempt to invoke virtual method 'void android.widget.TextView.setVisibility(int)' on a null object reference
Edit 2 - I load PostHelper class using the following:
mPostHelper = new PostHelper(this, PostSolicitationActivity.this);
You're supposed to make sure that SolicitationAdapter is a RealmRecyclerViewAdapter, like so:
public class SolicitationAdapter extends RealmRecyclerViewAdapter<SolicitationDatabase, SolicitationViewHolder> {
public SolicitationAdapter(OrderedRealmCollection<SolicitationDatabase> results) {
super(results, true);
}
...
}
And then what you need to do is that you put the RealmResults as a field reference in your Activity:
public class PostSoliticiationActivity extends AppCompatActivity {
RealmResults<Solicitation> results;
Realm realm;
RealmChangeListener<RealmResults<Solicitiation> realmChangeListener = (results) -> {
if(results.isLoaded() && results.isValid()) {
if(results.isEmpty()) {
mNothingHere.setVisibility(View.GONE);
mRecycler.setVisibility(View.VISIBLE);
} else {
mNothingHere.setVisibility(View.VISIBLE);
mRecycler.setVisibility(View.GONE);
}
}
}
SolicitationAdapter adapter;
#Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.soliticiation_activity);
// bind views
realm = Realm.getDefaultInstance();
results = realm.where(SolicitationDatabase.class).findAllSortedAsync("id");
// .sort("id").findAllAsync(); in 4.3.0+
results.addChangeListener(realmChangeListener);
adapter = new SoliticiationAdapter(results);
mRecycler.setAdapter(adapter);
// layout manager as well
}
#Override
public void onDestroy() {
results.removeChangeListener(realmChangeListener);
realm.close();
super.onDestroy();
}
}
So things you don't need:
1.) reLoad() method
2.) onPostAdded callback
3.) PostActionListener
As long as you just add the SoliticiationDatabase to the Realm in a transaction, it'll all work without manually syncing ui.
If I understand correctly, you want to be notified in another view when an action happens elsewhere.
The way to do that is usually interfaces.
public class PostHelper {
// Define these
public interface PostActionListener {
void onPostAdded();
}
private PostActionListener postListener;
public void setPostActionListener(PostActionListener listener) throws ClassCastException {
this.postListener = (PostActionListener) context;
}
public PostHelper(Context context) {
this.mContext = context;
// Cast the passed context as a listener
if (mContext instanceof PostActionListener) {
this.postListener = (PostActionListener) mContext;
}
}
public String addSolicitation(File _file, boolean fromQueue) {
// Do something
// Callback to the UI to update
if (this.postListener != null) {
this.postListener.onPostAdded();
}
}
Then, in your initial Activity
public class YourActivity extends AppCompatActivity
implements PostHandler.PostActionListener {
// ... fields
#Override
public void onPostAdded() {
reLoad();
}
public void reLoad() {
boolean emptyList = myRealm.where(SolicitationDatabase.class).findAll().isEmpty();
mNothingHere.setVisibility(emptyList ? View.VISIBLE : View.GONE);
mRecycler.setVisibility(emptyList ? View.GONE : View.VISIBLE);
}
#Override
public void onCreate(Bundle b) {
...
mPostHelper = new PostHelper(this);
}
However, since you are using Realm, and there really is no data that you need to "return" here, then you can simply let the Android Activity lifecycle refresh the data for you.
I have a weird / unique situation with my ListView. This is the scenario:
I'm making use of the MVP design pattern. As the Activity starts, it raises an event to notify the presenter to fetch some data from a web service. The web service call is an Async call. Once the web service Completed event is raised, I take the result and push it into a property (which is of type Array) that resides within my View / Activity.
Everything I mentioned works just fine, but as soon as the device is rotated, some interesting developments take place.
The async call resumes as normal and provides the property (Array) with a value. So nothing wrong there... (And yes there is data in the collection) I then set the ListView Adapter and call the notifyDataSetChanged, but nothing happens. The UI is not updated or anything?? If I re-enter the Activity the data is visible again ??
I even tried calling invalidateViews and invalidate on the ListView - this didn't do anything.
Could someone please assist me in this matter?
Many thanks in advance!
[Update]
I would like to stress the fact that I am making use of C# (Xamarin) and not Java (:sigh: - yes I know). Furthermore, I am not making use of the ASyncTask class, instead I'm making use of the async methods created within the proxy classes generated by Visual Studio. Pretty straight forward, but this is the code that populates the ListView - the property is set from the presenter
Presenter
Where View is of type IContactsView
protected override void OnCollectData(System.Collections.IEnumerable data, Type typeOfData)
{
if (data != null && typeOfData != null && typeOfData.Equals(typeof(UserContact)))
{
this.View.UserInformationCollection = data.Cast<UserContact>().ToArray();
}
}
Activity
The activity implements IContactsView
public UserContact[] UserInformationCollection
{
get
{
return this._userInformationCollection;
}
set
{
this.RunOnUiThread(() =>
{
this._userInformationCollection = value;
ListView listview = this.FindViewById<ListView>(Resource.Id.userLV);
if (listview != null)
{
UserContact[] subsidiesList = this.GetIndexedContacts(this._userInformationCollection);
listview.Adapter = new ContactsAdapter(this, subsidiesList.ToList());
((ContactsAdapter)listview.Adapter).NotifyDataSetChanged();
}
});
}
}
[/Update]
Found a much better solution! So please ignore the static variable idea!
Activity:
Override the OnRetainNonConfigurationInstance and return the presenter
public override Java.Lang.Object OnRetainNonConfigurationInstance()
{
return this._presenter;
}
Within the OnCreate check the LastNonConfigurationInstance and get the presenter - if it isn't null:
protected override void OnCreate(Bundle bundle)
{
...
if (this.LastNonConfigurationInstance != null)
{
this._presenter = this.LastNonConfigurationInstance as ContactsPresenter;
this._presenter.RefreshView(this);
}
else
{
// create a new presenter
this._presenter = new ContactsPresenter(this);
}
...
}
So maybe, you saw what I did in the previous code sample? Yes, I send the new instance of the activity to the presenter - have a look at the RefreshView
Presenter:
So within my base presenter I have the following method:
public class Presenter<T> : Java.Lang.Object, IPresenter where T : IView
{
/// <param name="view">The view.</param>
public void RefreshView(T view)
{
this.View = view;
}
}
The above code helps my presenter say with the creation of new activities - so when it returns data after the async call it will have the latest and greatest instance of the activity!
Hope this helps!
Kind regards,
Got it working by doing the following:
declare a static variable of the activity:
private static ContactsActivity _cachedActivity = null;
Overrode the OnResume within the activity and set the variable:
protected override void OnResume()
{
base.OnResume();
_cachedActivity = this;
}
Override the OnCreate within the activity and set the variable:
protected override void OnCreate(Bundle bundle)
{
...
_cachedActivity = this;
...
}
Lastly I changed the property mentioned earlier:
public USBUserContact[] UserInformationCollection
{
get
{
return this._userInformationCollection;
}
set
{
_cachedActivity.RunOnUiThread(() =>
{
_cachedActivity._userInformationCollection = value;
ListView listview = _cachedActivity.FindViewById<ListView>(Resource.Id.userLV);
if (listview != null)
{
UserContact[] subsidiesList = _cachedActivity.GetIndexedContacts(_cachedActivity._userInformationCollection);
listview.Adapter = new ContactsAdapter(_cachedActivity, subsidiesList.ToList());
((ContactsAdapter)listview.Adapter).NotifyDataSetChanged();
}
});
}
}
Kind regards,