ListView not updating after async call - android

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,

Related

Refresh data with RxJava and Retrofit

I'm using Retrofit with RxJava2 to obtain some data from a Rest API. I want to use a SwipeRefreshLayout to update the view and I'm using a ViewModel to handle the API call, so I want to implement a method in there to refresh the data programmatically.
I want to obtain something like this https://stackoverflow.com/a/34276564/6787552 but instead of having a periodic trigger, I want to do that programmatically when the user pull to refresh.
That's the ViewModel:
public class DashboardViewModel extends ViewModel {
public final Single<Dashboard> dashboard;
public DashboardViewModel() {
dashboard = Api.getDashboard();
refresh();
}
public void refresh() {
// Refresh data
}
}
And in the DashboardFragment:
#Override
public View onCreateView(...) {
...
viewModel.dashboard
.observeOn(AndroidSchedulers.mainThread())
.subscribe(dashboard -> {
binding.setDashboard(dashboard);
binding.swipeRefreshLayout.setRefreshing(false);
});
binding.swipeRefreshLayout.setOnRefreshListener(() -> viewModel.refresh());
...
}
Thank you in advance!
EDIT:
That's what I ended up doing:
public class DashboardViewModel extends ViewModel {
private final BehaviorSubject<Dashboard> dashboard;
public DashboardViewModel() {
dashboard = BehaviorSubject.createDefault(Api.getDashboard());
}
public void refresh() {
// I use a Object because null values are not supported
dashboard.onNext(Api.getDashboard());
}
public Observable<Dashboard> getDashboard(){
return dashboard;
}
}
And then in the DashboardFragment just subscribe to viewModel.getDashbaord()
I'm not 100% sure that I understood what you want to do but if I got the question right, you can do something like this:
put a subject inside the model (probably a BehaviorSubject?)
expose it as an observable to the
view and subscribe to it (instead of subscribing to the single)
in the model, when you
receive a new call to refresh() from the ui, do something like
subject.onNext(Api.getDashboard())
in this way, each call to refresh will cause the emission of a new dashboard, and that will be properly bound by the subscription in the view.

Create boolean listener

In Android, how do I take an action whenever a variable changes?
So I want to implement a listener for an object I created. What I want it to do is execute a block of code when its value changes from false to true.
As I am following this thread, I can't understand where the person wants us to implement the last block of code containing the logic for the listener.
Could someone, hopefully, guide me in the right direction?
(This question is being asked here as I don't have enough rep. points)
That last bit of example code triggers the listener, so it basically needs to be run whenever the "event" occurs. In this case the "event" is whenever (wherever in the code) the value of the variable changes.
If you have a setter and that is the only place the value changes, that is where you'd put it. If you are changing the value in multiple places throughout your code, I would make a new private method (call it signalChanged), put your code there, and then call it immediately after the variable assignment in the cases you want the listener to fire.
Here's an example (some code borrowed from linked answer, haven't checked that it compiles).
public class MyObj
{
public MyObj(int value)
{
setValue(value);
}
private int myValue;
public int getValue() { return myValue; }
public void setValue( int value )
{
if (value != myValue)
{
myValue = value;
signalChanged();
}
}
public interface VariableChangeListener
{
public void onVariableChanged(Object... variableThatHasChanged);
}
private VariableChangeListener variableChangeListener;
public void setVariableChangeListener(VariableChangeListener variableChangeListener)
{
this.variableChangeListener = variableChangeListener;
}
private void signalChanged()
{
if (variableChangeListener != null)
variableChangeListener.onVariableChanged(myValue);
}
}
you have to create a callback interface
here is a good about custom listener tutorial
here is a sample
public class MyObj {
VariableChanger onVariableChanged ;
public void setOnVariableChanged(VariableChanger onVariableChanged) {
this.onVariableChanged = onVariableChanged;
}
void log(){
boolean changed = false;
onVariableChanged.onVariableChanged();
//this will call it
}
interface VariableChanger{
void onVariableChanged();
}
}
class logic {
MyObj mo = new MyObj();
void main(){
mo.setOnVariableChanged(new MyObj.VariableChanger() {
#Override
public void onVariableChanged() {
//do your action
}
});
}
}
In Android, like any language, most developper uses logic comparisons to check values (if, else, switch, =, !=, >, <, etc) or Event (signal)
What kind of listener do you want to implement?

RxJava as event bus?

I'm start learning RxJava and I like it so far. I have a fragment that communicate with an activity on button click (to replace the current fragment with a new fragment). Google recommends interface for fragments to communicate up to the activity but it's too verbose, I tried to use broadcast receiver which works generally but it had drawbacks.
Since I'm learning RxJava I wonder if it's a good option to communicate from fragments to activities (or fragment to fragment)?. If so, whats the best way to use RxJava for this type of communication?. Do I need to make event bus like this one and if that's the case should I make a single instance of the bus and use it globally (with subjects)?
Yes and it's pretty amazing after you learn how to do it. Consider the following singleton class:
public class UsernameModel {
private static UsernameModel instance;
private PublishSubject<String> subject = PublishSubject.create();
public static UsernameModel instanceOf() {
if (instance == null) {
instance = new UsernameModel();
}
return instance;
}
/**
* Pass a String down to event listeners.
*/
public void setString(String string) {
subject.onNext(string);
}
/**
* Subscribe to this Observable. On event, do something e.g. replace a fragment
*/
public Observable<String> getStringObservable() {
return subject;
}
}
In your Activity be ready to receive events (e.g. have it in the onCreate):
UsernameModel usernameModel = UsernameModel.instanceOf();
//be sure to unsubscribe somewhere when activity is "dying" e.g. onDestroy
subscription = usernameModel.getStringObservable()
.subscribe(s -> {
// Do on new string event e.g. replace fragment here
}, throwable -> {
// Normally no error will happen here based on this example.
});
In you Fragment pass down the event when it occurs:
UsernameModel.instanceOf().setString("Nick");
Your activity then will do something.
Tip 1: Change the String with any object type you like.
Tip 2: It works also great if you have Dependency injection.
Update:
I wrote a more lengthy article
Currently I think my preferred approach to this question is this to:
1.) Instead of one global bus that handles everything throughout the app (and consequently gets quite unwieldy) use "local" buses for clearly defined purposes and only plug them in where you need them.
For example you might have:
One bus for sending data between your Activitys and your ApiService.
One bus for communicating between several Fragments in an Activity.
One bus that sends the currently selected app theme color to all Activitys so that they can tint all icons accordingly.
2.) Use Dagger (or maybe AndroidAnnotations if you prefer that) to make the wiring-everything-together a bit less painful (and to also avoid lots of static instances). This also makes it easier to, e. g. have a single component that deals only with storing and reading the login status in the SharedPreferences - this component could then also be wired directly to your ApiService to provide the session token for all requests.
3.) Feel free to use Subjects internally but "cast" them to Observable before handing them out to the public by calling return subject.asObservable(). This prevents other classes from pushing values into the Subject where they shouldn't be allowed to.
Define events
public class Trigger {
public Trigger() {
}
public static class Increment {
}
public static class Decrement {
}
public static class Reset {
}
}
Event controller
public class RxTrigger {
private PublishSubject<Object> mRxTrigger = PublishSubject.create();
public RxTrigger() {
// required
}
public void send(Object o) {
mRxTrigger.onNext(o);
}
public Observable<Object> toObservable() {
return mRxTrigger;
}
// check for available events
public boolean hasObservers() {
return mRxTrigger.hasObservers();
}
}
Application.class
public class App extends Application {
private RxTrigger rxTrigger;
public App getApp() {
return (App) getApplicationContext();
}
#Override
public void onCreate() {
super.onCreate();
rxTrigger = new RxTrigger();
}
public RxTrigger reactiveTrigger() {
return rxTrigger;
}
}
Register event listener wherever required
MyApplication mApp = (App) getApplicationContext();
mApp
.reactiveTrigger() // singleton object of trigger
.toObservable()
.subscribeOn(Schedulers.io()) // push to io thread
.observeOn(AndroidSchedulers.mainThread()) // listen calls on main thread
.subscribe(object -> { //receive events here
if (object instanceof Trigger.Increment) {
fabCounter.setText(String.valueOf(Integer.parseInt(fabCounter.getText().toString()) + 1));
} else if (object instanceof Trigger.Decrement) {
if (Integer.parseInt(fabCounter.getText().toString()) != 0)
fabCounter.setText(String.valueOf(Integer.parseInt(fabCounter.getText().toString()) - 1));
} else if (object instanceof Trigger.Reset) {
fabCounter.setText("0");
}
});
Send/Fire event
MyApplication mApp = (App) getApplicationContext();
//increment
mApp
.reactiveTrigger()
.send(new Trigger.Increment());
//decrement
mApp
.reactiveTrigger()
.send(new Trigger.Decrement());
Full implementation for above library with example -> RxTrigger

Android - Return value get in a custom listener

Please excuse my french english !
So, I have got a problem in my Android code...
I call a method which much return a arrayList of a custom object... but this arrayList is loaded asynchronously and returned in a listener.
Do you know how I could return datas when the method of my listener is called ?
Here my code :
public static ArrayList<Advert> getAdverts(Context context) {
// Initialize
RestHelper restHelper = new RestHelper();
// Set the listener
restHelper.setOnRestListener(new OnRestListener<Advert>() {
#Override
public void onDataAvailable(ArrayList<Advert> result) {
// -- Datas are loaded : now we must return them ! --
}
});
// Launch the async query
restHelper.getRemoteAdverts();
}
Thanks !!
I'm not sure about the problem. You have an async method that you can call, but it can't return your result since its an async method, so you have 2 solutions :
- Wait in the method that all data are available, but you loose the async benefits.
- Implements an Oberserver/Observable pattern.
For the first option, look into the RestHelper, maybe you have already the solution.
Example for the second option :
public static void requestAdverts(Context context) {
RestHelper restHelper = new RestHelper();
final AdvertListener thisInstance = this;
restHelper.setOnRestListener(new OnRestListener<Advert>() {
#Override
public void onDataAvailable(ArrayList<Advert> result) {
thisInstance.notifyDataLoaded(result);
}
});
// Launch the async query
restHelper.getRemoteAdverts();
Create an Interface :
interface AdvertListener {
notifyDataLoaded(ArrayList<Advert> result);
}
And finally let the main class (the one that call the requestAdverts method) implements your new interface.
Well, please revisit your function design, you are tiring to make asynchronously load inside a synchronous function (getAdverts). If your function is synchronous, then just synchronously load the list and return.
If for any reason, if you want to go ahead with current approach, please block the caller after setOnRestListener and when you get a callback(onDataAvailable) unlock it and return your list.
You can use ConditionVariable, for this.
Block the caller:
ConditionVariable.block ();
ConditionVariable.close ();
UnBlock/open the caller:
ConditionVariable.open ();
Hope, this helps.

Android async task - which activity evoked it?

I am facing problem of having one async task, but I need it use twice, because each time I change different part of GUI (updating progress bar).
Is there any way how to determine in if - else clause, which activity does it call and then make appropriate function for each of both of them?
Edit: huh, answer was here and now there isn't...
Thanks
You can hold a member variable which contains the activity/context it is started from.
//pseudocode
AsyncTask task = new AsyncTask();
task.mActivity = this;
task.execute();
Inside doInBackground just check the activity:
//pseudocode
if (mActivity instanceof MyActivity) {
// ....
} else {
// ....
}
Extract the code from the AsyncTask implementation and delegate that to the Activity. Example:
public interface MyDelegate {
public void updateProgress(....)
}
Your AsyncTask takes a delegate and calls it when appropiate:
public class MyAsyncTask .... {
public MyAsyncTask(MyDelegate myDelegate) { ... }
// somewhere in your code (probably onProgressUpdate)
myDelegate.updateProgress(...)
}
Your Activity/ies implement/s the delegate:
public class MyActivity extends Activity implements MyDelegate {
public void updateProgress(...) {
// update ui
}
// somewhere in your code:
new MyAsyncTask(this).execute(...);
}

Categories

Resources