Android LiveData null error when trying to update object - android

I am looking for help to better understand LiveData in Android.
I have created an application that allows a user to view, edit or create entries in a db.
I have used a viewmodel on the form activity to share information between the activity and fragments and to also make the data Lifecycle aware.
The code works great when I load a entry from the database. Any changes are captured and saved back to the database
However when I want to create a new entry using the same code I get
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method ...null object reference when I try to update the object in the viewmodel. Sample below tries to set the Name value when a user enters a new one:
public void afterTextChanged(Editable s) {
patternViewModel.getPattern().getValue().setName(s.toString());
((FlyPatternDetailActivity)getActivity()).setHasChanged(true);
((FlyPatternDetailActivity)getActivity()).setActionBar(s.toString());
}
My question is how to update the livedata object of my entity if I dont get an object back from my database and I am going to create a new entry.
My viewmodel is:
public class PatternViewModel extends AndroidViewModel {
private PatternRepository repo;
private LiveData<FlyPattern> mObservablePattern;
public PatternViewModel(#NonNull Application application) {
super(application);
if (mObservablePattern == null) {
mObservablePattern = new MutableLiveData<>();
}
repo = PatternRepository.getInstance(((FlyTyingJournalApplicationClass) application).getDatabase());
}
public void loadPattern(final long id){
if(id != -1)
mObservablePattern = repo.getPattern(id);
}
public LiveData<FlyPattern> getPattern(){
return mObservablePattern;
}
public void insertPattern() {
repo.insertPattern(mObservablePattern.getValue());
}
public void updateFlyPattern(){
repo.updatePattern(mObservablePattern.getValue());
}
public void deleteFlyPattern(){
repo.deletePattern(mObservablePattern.getValue());
}
}
I understand why I am getting the nullpointException.
My question is how to instantiate my mObservablePattern so I can update it.
It gets instantiated if the loadPattern returns an object.
If the loaddata does not run or does return an object from the database then I cannot update mObservablePattern.
Stacktrace from the Error:
Process: com.rb.android.flytyingjournal, PID: 4957
java.lang.NullPointerException: Attempt to invoke virtual method 'void com.rb.android.flytyingjournal.entities.FlyPattern.setType(java.lang.String)' on a null object reference
at com.rb.android.flytyingjournal.flypatterndetails.PatternDetailsFragment$5.onItemSelected(PatternDetailsFragment.java:325)
at android.widget.AdapterView.fireOnSelected(AdapterView.java:931)
at android.widget.AdapterView.dispatchOnItemSelected(AdapterView.java:920)
at android.widget.AdapterView.-wrap1(AdapterView.java)
at android.widget.AdapterView$SelectionNotifier.run(AdapterView.java:890)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6119)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)

You are trying to call setName() on a NULL. Because patternViewModel.getPattern().getValue() returns the value that is held in the LiveData, which might be NULL in your case. You can add a null check:
if (patternViewModel.getPattern().getValue() == null) {
FlyPattern flyPattern = new FlyPattern();
flyPattern.setName("foo");
patternViewModel.getPattern().setValue(flyPattern);
} else {
patternViewModel.getPattern().getValue().setName("foo");
}
Or you can create a function in the ViewModel called e.g. setFlyPatternName() and use it to update your DB.
In your PatternViewModel.java
public void setFlyPatternName(String name) {
if (mObservablePattern.getValue == null) {
FlyPattern flyPattern = new FlyPattern();
flyPattern.setName(name);
mObservablePattern.setValue(flyPattern);
repo.insertFlyPattern();
} else {
mObservablePattern.getValue().setName(name);
repo.updateFlyPattern();
}
}
Edit: The proper way of doing this is actually a bit different.
Normally your repository functions need to work on the background thread, since you are dealing with i/o, but if you want them to work on the mainThread you need to at least create a callback and pass that callback object to your repository function, which will be called when the data is inserted/updated/deleted. And when the callback function is called you need to call the setValue() on the LiveData and set the data to your livedata object.
Create a callback interface:
public interface InsertCallback {
void onDataInserted(FlyPattern flyPattern);
void onDataInsertFailed();
}
Change your repositories insert function's body to accept the Callback object as parameter
public void insertFlyPattern(FlyPattern flyPattern, InsertCallback insertCallback) {
// Do your insertion and if it is successful call insertCallback.onDataInserted(flyPattern), otherwise call insertCallback.onDataInsertFailed();
}
In your ViewModel implement InsertCallback and it's method
public class PatternViewModel extends AndroidViewModel implements InsertCallback {
//....
public void onDataInserted(FlyPattern flyPattern) {
mObservablePattern.setValue(flyPattern);
}
public void onDataInsertFailed() {
//Show error message
}
}

Related

Android UI testing with mocked dependencies

I have an activity in which I provide a button. Clicking on the button invokes a method in a data provider class and based on the return value of the method I make UI changes. Now I want to write an instrumented test where I perform click() in the button but avoid actually calling the method in the data provider class. Instead I want to return a desired value from the method and then check if the UI was modified accordingly.
MyActivity
#Override
public void onCreate(final Bundle savedInstanceState) {
mActionButton.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(final View v) {
boolean result = dataProvider.getResult();
if(result) {
mSuccessTextView.setVisibility(View.VISIBLE);
}
}
});
}
Here, on button click, a call is made to DataProvider#getResult and the result from this method is stored in result. If the result is true a TextView mSuccessTextView, previously GONE, is now made VISIBLE.
The problem here is DataProvider#getResult deals with a lot of external components that would make testing impossible. So what I want to do is use a mocked instance of DataProvider so that I can get getResult to return a desired value and then check the visibility of mSuccessTextView. This is what I tried :
MyActivityTest.java
#RunWith(AndroidJUnit4.class)
public class MyActivityTest {
private DataProvider mDataProvider;
#Rule
public IntentsTestRule<MyActivity> mIntentRule =
new IntentsTestRule<>(MyClientActivity.class);
#Before
public void setUp() {
mDataProvider = mock(DataProvider.class);
}
#Test
public void testResultSuccess() {
boolean result = true;
when(mDataProvider.getResult()).thenReturn(result);
onView(withId(R.id.action_button)).perform(click());
onView(withId(R.id.success_text_view)).check((ViewAssertion) isDisplayed());
}
}
Doing the above generates the following error :
org.mockito.exceptions.base.MockitoException:
Mockito cannot mock this class: class com.domain.myapp.DataProvider.
Mockito can only mock non-private & non-final classes.
If you're not sure why you're getting this error, please report to the mailing list.
Underlying exception : java.lang.UnsupportedOperationException: Cannot define class using reflection
.
.
.
Caused by: java.lang.UnsupportedOperationException: Cannot define class using reflection
.
.
.
Caused by: java.lang.IllegalStateException: This JVM's version string does not seem to be valid: 0
.
.
.
Even if you could mock DataProvider, it would not help you because you are not injecting its instance into MyClientActivity during your test. Reasons for not able to mock DataProvider are unknown, pls provide the class.

What happens if I close my android application without closing an open realm instance?

I have kept a single realm instance opened on main thread in Application class and I use that single instance to do all kinds of DB operations from MainActivity. Since my application has a single activity, I close the instance in the activity's onDestroy(). The app is working fine for me as of now.
What are the repercussions of not doing a realm.close()? My database hasn't corrupted with or without the same.
Also, I've read that there are scenarios in which the Activity's onDestroy() may not get called at all. What effects the database can have in such a scenario if closing realm is so important?
public class MyApp extends Application {
private static MyApp instance;
private Realm realm;
public void onCreate() {
super.onCreate();
Realm.init(this);
Realm.setDefaultConfiguration(new RealmConfiguration.Builder()
.schemaVersion(BuildConfig.VERSION_CODE)
.migration(new RealmMigrationClass())
.compactOnLaunch()
.build());
realm = Realm.getInstance(Realm.getDefaultConfiguration());
}
public static MyApp getInstance() {
return instance;
}
public Realm getRealm() {
return realm;
}
}
MainActivity
public class MainActivity extends Activity {
#Override
protected void onDestroy() {
MyApp.getInstance().getRealm().close();
super.onDestroy();
}
}
Closing the realm instance is very important because of realm core has been written in c++ programming language and is compiled in the native code.And we know the c++ garbage collection does not run automatically we require to manually call the garbage collection.So when you call the realm.close() it means that realm deallocation the native memory means free or delete the pointer variable and also do the file descriptor job.From realm.close() means you give the command or tell to native c++ compiler to run the garbage collection.
If you look the "doc" (REALM_DOC) for Realm for Java you can find:
Realm implements Closeable to take care of native memory deallocation
and file descriptors, so always close your Realm instances when you’re
done with them.
Realm instances are reference counted—if you call getInstance twice in
a thread, you need to call close twice as well. This allows you to
implement Runnable classes without having to worry about which thread
will execute them: simply start it with getInstance and end it with
close.
Personally I suggest you to define a class in which define your Realm functions and an "Realm attribute" (like a "RealmHelper" class) then inside this class define:
- a unstatic Realm
- a static RealmHelper instance
You will always use this RealmHelper static instance for all operations in your Realm inside your main Thread, inside other threads you will call "new RealmHelper()" and CLOSE the realm just after you did the operation.
Doing this in your MainThread you just need to close ONE realm instance when the application get closed, to do this you can use the "Application.ActivityLifecycleCallbacks" interface inside a Custom defined Application class (so which extends Application of Android).
Example inside you Application custom class:
/* START Override ActivityLifecycleCallbacks Methods */
#Override
public void onActivityCreated(Activity activity, Bundle bundle) {
}
#Override
public void onActivityStarted(Activity activity) {
// Check if your MyRealmClass instance is null or is closed, in this case
// re-initialize it.
if(MyRealmClass.getInstance() == null || MyRealmClass.getInstance().getRealm().isClosed()){
MyRealmClass.initInstance();
}
}
#Override
public void onActivityResumed(Activity activity) {
}
#Override
public void onActivityPaused(Activity activity) {
}
#Override
public void onActivityStopped(Activity activity) {
if(!AppUtils.isAppOnForeground(this)){
// Close your MyRealmClass instance
if(MyRealmClass.getInstance() != null) {
MyRealmClass.getInstance().close();
MyRealmClass.getInstance().logRealmInstanceCount(LABEL_APP_IN_BACKGROUND);
MyRealmClass.setMyInstance(null);
}
}
}
#Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
}
#Override
public void onActivityDestroyed(Activity activity) {
}
/* END Override ActivityLifecycleCallbacks Methods */
Code of "isAppOnForeground" (check if your app is in foreground, if is not this mean your app is being closed):
public static boolean isAppOnForeground(Context context) {
boolean ret = false;
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
if(appProcesses != null){
String packageName = context.getPackageName();
for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND && appProcess.processName.equals(packageName)) {
ret = true;
}
}
}
return ret;
}
Your "MyRealmClass" will look like:
public class MyRealmClass {
protected Realm mRealm;
protected static MyRealmClass mInstance;
public MyRealmClass() {
mRealm = Realm.getDefaultInstance();
}
public static MyRealmClass initInstance(){
if(mInstance == null){
mInstance = new MyRealmClass();
}
return mInstance;
}
public static MyRealmClass getInstance(){
return mInstance;
}
public static void setMyInstance(MyRealmClass instance) {
mInstance = instance;
}
public Realm getRealm() {
return mRealm;
}
public void setRealm(Realm realm){
this.mRealm = realm;
}
public void close() {
if (mRealm != null) {
try {
mRealm.close();
} catch(Exception e){
onException(e);
}
}
}
[...]
Then you need to check that all your Realm instance is not closed when you use a RealmObject or you do some operation in your Realm. And if it is closed (because the app got in background and then restarted) you need to re-initialize the realm (if you have an activity with a MyRealmClass instance as attribute).
Example in a BaseMyActivity:
public abstract class MyBaseActivity extends AppCompatActivity {
protected MyRealmClass mRealmClass;
/* START Override Lifecycle Methods */
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initMyRealmClass();
Lyra.instance().restoreState(this, savedInstanceState);
}
#Override
protected void onStart() {
super.onStart();
initMyRealmClass();
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Lyra.instance().saveState(this, outState);
}
/* END Override Lifecycle Methods */
/* START Private Methods */
protected void initMyRealmClass(){
if(mRealmClass == null || mRealmClass.getRealm().isClosed()){
mRealmClass = MyRealmClass.initInstance();
}
}
/* END Private Methods */
}
Basically all your activities will extend this BaseActivity if they need to use Realm functions. (Lyra is used to save the state of any of your attributes: LYRA)
REMEMBER THAT:
if you set or get some attributes from a RealmObject or you get an object from a RealmList or RealmResults you NEED THAT YOUR REALM INSTANCE, from which the object was take, IS OPEN.
OTHERWISE you need to use this method when you init a variable with objects from the realm: (this methods should be placed in yuour "MyRealmClass")
public <T extends RealmObject> List<T> toList(RealmResults<T> results) {
return mRealm.copyFromRealm(results);
}
public <T extends RealmObject> List<T> toList(RealmList<T> results) {
return mRealm.copyFromRealm(results);
}
public <T extends RealmObject> T copyObjectFromRealm(T obj) {
return mRealm.copyFromRealm(obj);
}
public <T extends RealmObject> RealmResults<T> findAllObject(Class<T> classObject) {
RealmQuery<T> query = mRealm.where(classObject);
return query.findAll();
}
Now if you need to get a List of "MyRealmObjectClass" objects and add them to an adapter you will do this:
List<MyRealmObjectClass> myObjects = mRealmClass.toList(mRealmClass.findAllObject(MyRealmObjectClass.class))
myAdapter.addAll(myObjects);
Doing this if you "get" or "set" an attribute after the Realm instance, from which you got the objects, was closed (for example after the app get to background and then restarted) you won't get an exception.
BUT if you "set" an attribute of your RealmObject this WON'T BE SET in the REALM INSTANCE, so to change the value of a RealmObject inside the Realm in this case you need to Save the object!
OTHERWISE if you have a RealmResults or a RealmObject which is still connected to the Realm, so you can directly change, inside a transaction, an attribute of it and it will be changed inside the Realm too.
To do a Realm Transaction I suggest you to follow the DOC in the first link and, if you don't need to close the Realm in the Finally block, enable lambda and do this:
mRealm.executeTransaction(
realm -> {
[do your Realm operations]
}
)
or you can also do:
public boolean doRealmOperation(Object... params){
AtomicBoolean ret = new AtomicBoolean(false);
mRealm.executeTransaction(
realm -> {
try{
[do your realm operation]
ret.set(true);
} catch(Exception e){
onException(e)
ret.set(false);
}
}
)
}
in this case you need to use the "AtomicBoolean" because you will set the value you want to return inside the transaction, but inside a transaction the value got from outside of the transaction itself (in this case the "ret" variable) MUST BE A FINAL variable. But you can't define "ret" as "final" and then set it again, so you need to use the "AtomicBoolean" to set the variable outside the transaction and set it again inside the transaction itself.
(You can also avoid this problem by using a temporary variable to get the "true/false" value inside the transaction and then set the "ret" variable using that "temp variable". But personally I prefer to use "AtomicBoolean" class which is, I think, safer and more clean than a temp variable)
Hope this is helpful,
see you by and happy coding! ;)
Realm implements Closeable to take care of native memory deallocation and file descriptors, so always close your Realm instances when you’re done with them.
Realm instances are reference counted—if you call getInstance twice in a thread, you need to call close twice as well.
From my personal experience not closing realm has not caused a lot of issues, in fact when I tried closing it at times it would cause an issue when the app went into the background and was then resumed which caused a crash due to realm instance being closed, I am not sure why a new instance of realm was not created in that case, might have been a bug.
As of now I follow the realm docs and close my realm instances until they cause an issue.
General coding practises suggest that anything that is opened should be safely closed.
Yes, it will get closed only if you called close() method on your application's destroy() method. Remember Realm implements Closeable in order to take care of native memory deallocation and file descriptors so it is important to close your Realm instances when you are done with them.
For further info visit this link.

How to call fragment method from other fragments? [duplicate]

This question already has answers here:
how to call function from other fragment class
(2 answers)
Closed 5 years ago.
In my application I want call fragment methods from other fragments, I write below codes but when call this method show me below error in logcat and Force close application.
For show this two fragments into activity I use TabLayout and ViewPager.
My Review Fragment codes:
public void getComments(final Context context) {
JsonObject requestBean = new JsonObject();
requestBean.addProperty("entityType", 1);
requestBean.addProperty("reviewType", 5);
requestBean.addProperty("reviewUserType", 2);
requestBean.addProperty("entityID", serialID);
requestBean.addProperty("celebrityId", 0);
requestBean.addProperty("pageIndex", 1);
requestBean.addProperty("pageSize", 10);
InterfaceApi api = ApiClient.getClient().create(InterfaceApi.class);
Call<CommentResponse> call = api.getComments(token, requestBean);
call.enqueue(new Callback<CommentResponse>() {
#Override
public void onResponse(Call<CommentResponse> call, Response<CommentResponse> response) {
if (response.body().getData() != null) {
if (response.body().getData().size() > 0) {
reviewMovieFrag_NoComment.setText("");
} else {
reviewMovieFrag_NoComment.setText(context.getResources().getString(R.string.noReviews));
SerialReview_CastProgress.setVisibility(View.GONE);
}
commentModel.clear();
commentModel.addAll(response.body().getData());
commentsListAdapter.notifyDataSetChanged();
reviewMovieFrag_newsCommentsRecyclerView.setAdapter(commentsListAdapter);
reviewMovieFrag_newsCommentsUserTypeText.setText(userTypeStr);
reviewMovieFrag_newsCommentsReviewTypeText.setText(reviewTypeStr);
reviewMovieFrag_Progress.setVisibility(View.GONE);
}
}
#Override
public void onFailure(Call<CommentResponse> call, Throwable t) {
reviewMovieFrag_Progress.setVisibility(View.GONE);
}
});
}
I want call this method (getComments method) into InfoFragment and for this I write this code :
new MovieDetail_reviewFragment().getComments(getActivity());
But in LogCat show me this error :
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference
at com.test.app.Fragments.MovieDetailFragments.MovieDetail_reviewFragment$6.onResponse(MovieDetail_reviewFragment.java:301)
at retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall$1$1.run(ExecutorCallAdapterFactory.java:68)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5349)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:908)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:703)
Show error for this line :
reviewMovieFrag_NoComment.setText(context.getResources().getString(R.string.noReviews));
ATTENTION : Dear moderators and dear users I know this error for NullPointer but I don't know how can I fix it?
I tried for fix this issue but I can't so I ask in StackOverFlow.
Please help me and don't give me negative points or duplicate my post!
Please help me, Thanks all
Here,
new MovieDetail_reviewFragment().getComments(getActivity());
Is creating your fragment class newly. You need to use all variables when Fragment class created at starting.
Use viewPager.setOffscreenPageLimit(fragmentNumber); - this will help you to create all fragment when tab initialized.
Then use instance to access any fragment method.
Declare at top of fragment class, private static FragmentClass instance = null;
inside your Fragment class override onCreate() and initialize instance,
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
instance = this;
}
Create getInstance() method in fragment contains your calling method,
public static FragmentClass getInstance(){
return instance;
}
Finally call method from another fragment,
FragmentClass.getInstance().yourMethod();
don't take it otherwise but what you are doing is not good design pattern,
you should use interfaces for communicating between any two fragments. It will make things easier for you. Look into this post Basic Communication between two fragments

Accessing resources in fragment background task returns illegalStateException often

I am making an asynchronous network request to fetch reviews and trailers.
If the reviews and trailers are present then they are set as TextView texts. If there are no reviews present then I am setting the textview test as "No reviews"(message_no_reviews) which is a predefined string variable in strings.xml file.
Now in order to display that text when no reviews are available I am fetching that using
getResources().getString(R.string.message_no_reviews)
The problem is that call to getResources().getString(R.string.message_no_reviews) returns java.lang.IllegalStateException: Fragment MovieDetailFragment{1fdfd76} not attached to Activity many times whereas call to getResources().getString(R.string.api_key) does not cause any Exception.
Following is the code snippet.
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getReviews();
getTrailers();
}
public void getReviews(){
Call<JsonObject> getReviewsCall = apiService.getReview(movie.getId(),getResources().getString(R.string.api_key));
getReviewsCall.enqueue(new Callback<JsonObject>() {
#Override
public void onResponse(Response<JsonObject> response, Retrofit retrofit) {
if (response.body() != null) {
JsonArray reviews=(JsonArray) response.body().get("results");
System.out.println(reviews);
if(reviews.size()>0) {
tvReviewText.setText(((JsonObject) reviews.get(0)).get("content").getAsString());
tvReviewAuthor.setText(((JsonObject) reviews.get(0)).get("author").getAsString());
}
else{
tvReviewAuthor.setText(getResources().getString(R.string.message_no_reviews));
tvReviewText.setText("");
}
}
}
#Override
public void onFailure(Throwable t) {
}
});
}
Any thoughts on why this might be happening?
below is the logcat
FATAL EXCEPTION: main
Process: com.vipin.www.popularmovies, PID: 21580
java.lang.IllegalStateException: Fragment MovieDetailFragment{1fdfd76} not attached to Activity
at android.support.v4.app.Fragment.getResources(Fragment.java:636)
at com.vipin.www.popularmovies.MovieDetailFragment$2.onResponse(MovieDetailFragment.java:168)
at retrofit.ExecutorCallAdapterFactory$ExecutorCallback$1.run(ExecutorCallAdapterFactory.java:86)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5221)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
Your fragment is not attached to activity means:your fragment is not visible to user at that time since you made a network call from a button it create a another background thread and then you either backStack fragment or remove it from activity . So when response comes to onResponse method its context not available.
So You should use:
Fragment.this.getResources().getString(R.string.message_no_reviews));
or create a boolean whether fragment is attached or not in onCreate() and onDestroy()
tvReviewAuthor.setText(getActivity().getResources().getString(R.string.message_no_reviews));
This should do the work You need Context Object to access String Resources
Use onAttach() method to know when your fragment is attached to activity and also get context in it and use it to call other methods like this
#Override
public void onAttach(Context context) {
super.onAttach(context);
activity = context;
}
and then you can do this
tvReviewAuthor.setText(activity.getResources().getString(R.string.message_no_reviews));

ListView not updating after async call

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,

Categories

Resources