in my app, it launches a fragment, in the onCreate method, I asynchronously make a web service call, which takes about 2 seconds for the data to return. But by that time, the fragment has already completed loading. As a result, there is no data to appear on the screen.
I tried to put a thread sleep for 2 seconds, but that didnt work either.
When the asynchronous call has completed, it should call a method on the fragment. You can achieve this by passing the fragment to the async call and having a callback method.
For example, assuming you are using AsyncTask:
public class MyFragment extends Fragment {
public void onCreate(Bundle b) {
new MyAsyncTask(this).execute(params);
}
public void updateResult(Result r) {
// update the views here
}
}
public MyAsyncTask extends AsyncTask<...> {
private final MyFragment fragment;
public MyAsyncTask(MyFragment fragment) {
this.fragment = fragment;
}
...
#Override
protected void onPostExecute(Result result) {
fragment.updateResult(result);
}
}
In this way, the fragment can load before the data is ready. Then the data can be populated when it is received.
Depending on your design, you may like to have a loading progress animation visible by default in your fragment, then hide it when you make the data visible in the callback.
Related
I am running into a problem with Android Mutable Live Data and it might be because of my poor understanding. The scenario is I have 3 lists in my Android tabs.
public void openSort(SortType sortType) {
mSortType.postValue(sortType);
}
public MutableLiveData<SortType> getOpenSort() {
return mSortType;
}
In my activity I have my first fragment open. In that I call the
public void openSort(SortType sortType)
Everything works fine so far. Then I go ahead and open another tab which has the second fragment. This second fragment in its
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SortOrderViewModel sortOrderViewModel = ViewModelProviders.of(getActivity()).get(SortOrder.class);
sortOrderViewModel.observe(this, new Observer() {
#Override
public void onChanged(SortType sortType) {
// Debug print
}
})
}
For some reason the onChanged of the second fragment gets called too. My assumption was that the onChanged method only gets called when the data changes in the view model, but it seems the data is stored by the LiveData and the observers are notified again.
Is there something that we can do such that the fragment is "only" notified when the mutable live data has changed?
I'm using new arch. components from google.
I have in Activity Login/Registering Fragments that are managed thanks to FragmentTransaction
Activity->RegisterFragment (with ViewPager) -> RegistrationSteps (adapter)
Inside RegisterFragment I have ViewPager.
I want that all pages inside ViewPager will use the same ViewModel.
Those are registration steps (RegistrationStepFragment) that takes parent RegistrationFragment LifecycleOwner that scoped ViewModel to it- I just wanted ViewModel to be scoped to this parent Fragment.
RegistrationFragment.class inhered from
public interface FragmentViewPagerListener<T extends LifecycleFragment> {
void nextPage();
T getLifecycleFragment();
}
RegistartionSteps (pages) inhered from
public abstract class RegisterStepFragment extends LifecycleFragment {
protected FragmentViewPagerListener mListener;
protected RegisterViewModel mViewModel;
public void setListener(FragmentViewPagerListener fragmentViewPagerListener) {
this.mListener = fragmentViewPagerListener;
}
protected abstract void observeViewModel();
#Override
public void onCreated(#Nullable Bundle savedInstanceState) {
super.onCreated(savedInstanceState);
mViewModel = ViewModelProviders.of(mListener.getLifecycleFragment()).get(RegisterViewModel.class);
observeViewModel();
}
protected abstract boolean validateData();
}
All goes well until I reach 3 page, and I want to move back (to second page) Then is thrown exception in mViewPager.setCurrentItem(1) (page 2: index: 1)
*java.lang.RuntimeException: Failed to call observer method
at android.arch.lifecycle.ReflectiveGenericLifecycleObserver.invokeCallback(ReflectiveGenericLifecycleObserver.java:79)
at android.arch.lifecycle.ReflectiveGenericLifecycleObserver.invokeMethodsForEvent(ReflectiveGenericLifecycleObserver.java:53)
at android.arch.lifecycle.ReflectiveGenericLifecycleObserver.invokeCallbacks(ReflectiveGenericLifecycleObserver.java:61)
at android.arch.lifecycle.ReflectiveGenericLifecycleObserver.onStateChanged(ReflectiveGenericLifecycleObserver.java:45)
at android.arch.lifecycle.LifecycleRegistry$ObserverWithState.sync(LifecycleRegistry.java:209)
at android.arch.lifecycle.LifecycleRegistry.handleLifecycleEvent(LifecycleRegistry.java:102)
at android.arch.lifecycle.Li*fecycleDispatcher.dispatchIfLifecycleOwner(LifecycleDispatcher.java:150)
EDIT
Ok, I've found out that when moving to previous page, Fragment was reCreated calling and mViewModel.observable() received previously SUCCESS message and caused viewpager to move forward, that causes to destroy just created fragment what was causing error.
Solution is to create SingleEventLiveData that emits values only when post is called (don't notify observer if value changed before observer attached)
I mark it as CLOSE
I have method in fragment activity and if that method trigger, I need to update fragment listView. I am dealing with database. Where I am clearing the database of particular user and i will update fragment.
Problem is: if user is in same screen means, how to update fragment listview if fragment activity method triggers? It only works when I need to go back to activity and once again need to come to same screen.
Here is code:
public class ActivityExpertDasboard extends ActivityBase {
// this method is calling when particular user closes the screen. when I am in fragment screen..
#Override
protected void onChatInvitation(String msgKeys, String userId) {
String msgKey = mApplication.returnEmptyStringIfNull(msgKeys);
LogMessage.e("username", mPreference.getStringFromPreference(Constants.CLOSE_CHAT_USERNAME));
if (userId.equalsIgnoreCase(mPreference.getStringFromPreference(Constants.CLOSE_CHAT_USERNAME))) {
if (msgKey.equalsIgnoreCase(Constants.CODE_CHAT_END)) {
AAEDatabaseHelper.deleteUsername(mPreference.getStringFromPreference(Constants.CLOSE_CHAT_USERNAME));
// I need to update in Fragment screen if this is triggered.
}
}
super.onChatInvitation(msgKey, userId);
}
}
FragmentExpertLiveChats:
public class FragmentExpertLiveChats extends Fragment {
private List<LiveChatDetails> list;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_chat_history, container,
Constants.ATTACH_TO_ROOT);
list = AAEDatabaseHelper.getLiveChatDetails(Constants.TABLE_LIVE_CHAT);
}
#Override
public void onStart() {
LogMessage.e("onStart", "onStart");
super.onStart();
updateUI();
}
}
If phone is in FragmentExpertLiveChat screen without doing any perfomance and if method in activity calls, how to update the row? I need to use broadcast receiver? If yes, where and how?
For that, and many more cases, where you need to communicate amongst different components, I suggest using EventBus. It's usage is very simple:
Define events: public class MessageEvent { /* Additional fields if
needed */ }
Prepare subscribers Register your subscriber (in your onCreate or in a
constructor): eventBus.register(this);
Declare your subscribing method: #Subscribe public void
onEvent(AnyEventType event) {/* Do something */};
Post events: eventBus.post(event);
Don't forget to unregister afterwards. I suggest you do registration/unregistration in start/stop or pause/resume, or, in case of fragments, attach/dettach.
In your case, register in Fragment, and in Activity, when user does his things, post event.
I am using navigation drawer, Fragment_1 holds a listview which searches for gps location and then loads the adapter. The process works fine if I keep the Fragment_1 open till it loads fully. But if I try to open another fragment Fragment_2 while Fragment_1 is searching for location or loading adapter, the my app crashes. Fragment_2 holds textview and works fine if initiated seperately.
I am using following code to launch new fragments from drawer
Fragment mFragment;
FragmentManager mFragmentManager = getSupportFragmentManager();
mFragment = new Fragment_2();
mFragmentManager.beginTransaction()
.replace(R.id.frame_container,mFragment)
.commit();
You should do an Async task for loading the list. Before replacing current fragment just cancel the async task. Make sure that you check in onPostExecute if the task is not canceled.
Here you can find an example of loading data async into a recycler view: http://javatechig.com/android/android-recyclerview-example . Have a look at AsyncHttpTask. You can see data is taken and parsed on doInBackground and is displayed in onPostExecute. You also need to add the following to your code: enclose everything in onPostExecute in
if (!isCancelled()) {
/* your code here for setting list adapter */
}
override on detach:
#Override
public void onDetach() {
super.onDetach();
// don't update the UI if user go from this fragment
if (displayResultsAsyncTask != null && !displayResultsAsyncTask.isCancelled())
displayResultsAsyncTask.cancel(true);
}
So your code should look something like that:
public class YourFragment extends Fragment {
// declare an async task in your fragment
private AsyncTask displayResultsAsyncTask = null;
/* other data here */
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
/* your code for onCreate */
GetAndDisplayResults(); // call display results
}
public void GetAndDisplayResults() {
displayResultsAsyncTask = new AsyncTask<String, Void, Integer>() {
#Override
protected Integer doInBackground(String... params) {
Integer result = 0;
// get and parse data, also set result
return result;
}
#Override
protected void onPostExecute(Integer result) {
if (!isCancelled()) {
// if task wasn't stopped
if (result == 1)
SetYourList(); // set your list adapter based on results returned from doInBackground
}
}
}.execute();
}
#Override
public void onDetach() {
super.onDetach();
// don't update the UI if user go from this fragment
if (displayResultsAsyncTask != null && !displayResultsAsyncTask.isCancelled())
displayResultsAsyncTask.cancel(true);
}
}
The list used to save your data can be declared globaly and accesed from both doInBackground and onPostExecute or can be pased to onPostExecute as a param.
It looks like that you are trying to commit fragmentTransaction in one of the onCreate, onResume methods which is causing exception IllegalStateException: Can not perform this action after onSaveInstanceState due to activity state loss. Please check whether you are doing in these functions.
Hope this helps.
I have an Activity in which I have a ProgressBar,an ImageView and a TextView,I update all three from an AsyncTask.All three get updated when the screen is completely in one orientation when the task is running,but the ImageView and TextView are not displayed and the ProgressBar freezes when the screen orientation changes from one orientation to another.
Adding the attach and detach methods to the task and using retainNonConfigurationInstance to return the task when the Activity and using getLastNonConfigurationInstance is destroyed has had no effect.I have also implement three methods for getting the various progress values from the AsyncTask to no effect.
MyActivity looks like this:
static final String TAG="ImageUpdateActivity";
TextView txt_currentOp;
ImageView img_currentOp;
ImageUpdatingTask task;
CustomProgressBar updatebar;
#SuppressWarnings("deprecation")
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_imageupdate);
txt_currentOp=(TextView)findViewById(R.id.txt_currentOp);
img_currentOp=(ImageView)findViewById(R.id.img_updateOp);
updatebar=(CustomProgressBar)findViewById(R.id.progressbar_update);
String filename=getIntent().getStringExtra("pathName");
task=(ImageUpdatingTask)getLastNonConfigurationInstance();
if(task!=null)
{
task.attach(this);
if(task.getStatus()==AsyncTask.Status.RUNNING)
{
Log.d(TAG, "The progress description is: "+task.getProgressDesc());
txt_currentOp.setText(task.getProgressDesc());
img_currentOp.setImageBitmap(task.getProgressBitmap());
updatebar.setProgress(task.getProgress());
}
}
else
{
task=new ImageUpdatingTask(this);
task.execute(filename);
}
}
public Object retainNonConfigurationInstance()
{
task.detach();
return task;
}
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
if(task.getStatus()!=AsyncTask.Status.FINISHED)
{
task.cancel(true);
task=null;
}
Intent i=new Intent(this,ImagePreviewActivity.class);
startActivity(i);
}
return super.onKeyDown(keyCode, event);
}
This is how I update the progress from my doInBackground method where
int progress=0;
Bitmap progressBitmap=null;
String progressDesc=null;
are global variables.
mOperation=BITMAP_TO_PIX;
progressDesc=getValueFromOperation(mOperation);
Pix pix=convertBitmapToPix(bitmap);
mOperation=CONVERT_TO_8;
progressDesc=getValueFromOperation(mOperation);
Pix pix2=convertOperation(pix);
temp=pix2.copy();
tempImg=convertPixToBitmap(temp);
progressBitmap=tempImg;
temp=null;
progress+=10;//60
publishProgress(tempImg);
And in my publishProgress I use:
#Override
protected void onProgressUpdate(Bitmap... values) {
// TODO Auto-generated method stub
super.onProgressUpdate(values);
int oldOperation=0,oldProgress=0;
if(mOperation!=oldOperation)
{
String progressText=getValueFromOperation(mOperation);
Log.d(TAG, progressText);
activity.txt_currentOp.setText(progressText);
oldOperation=mOperation;
}
if(oldProgress!=progress)
{
Log.d(TAG,"Update the progress: "+progress);
activity.updatebar.setProgress(progress);
oldProgress=progress;
}
activity.img_currentOp.setImageBitmap(values[0]);
}
And the Activity,is passed to the task using the constructor:
public ImageUpdatingTask(ImageUpdateActivity activity)
{
this.activity=activity;
}
These are the methods that take care of interaction between the AsyncTask and the Activity:
public void attach(ImageUpdateActivity activity)
{
this.activity=activity;
}
public void detach()
{
activity=null;
}
public int getProgress()
{
return progress;
}
public Bitmap getProgressBitmap()
{
return progressBitmap;
}
public String getProgressDesc()
{
return progressDesc;
}
When orientation changes your activity gets is destroyed and recreated. Fragments are hosted by an activity.
By default, Fragments are destroyed and recreated along with their parent Activitys when a configuration change occurs. Calling Fragments setRetainInstance(true) allows us to bypass this destroy-and-recreate cycle, signaling the system to retain the current instance of the fragment when the activity is recreated.
public void setRetainInstance (boolean retain)
Added in API level 11
Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change). This can only be used with fragments not in the back stack. If set, the fragment lifecycle will be slightly different when an activity is recreated:
onDestroy() will not be called (but onDetach() still will be, because the fragment is being detached from its current activity).
onCreate(Bundle) will not be called since the fragment is not being re-created.
onAttach(Activity) and onActivityCreated(Bundle) will still be called.
You can check this blog for a workaround suggested . Uses interface as callback to the activity.
http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html
and the source code for the same is available at
https://github.com/alexjlockwood/worker-fragments
Quoting from the blog
Flow of Events
When the MainActivity starts up for the first time, it instantiates and adds the TaskFragment to the Activity's state. The TaskFragment creates and executes an AsyncTask and proxies progress updates and results back to the MainActivity via the TaskCallbacks interface. When a configuration change occurs, the MainActivity goes through its normal lifecycle events, and once created the new Activity instance is passed to the onAttach(Activity) method, thus ensuring that the TaskFragment will always hold a reference to the currently displayed Activity instance even after the configuration change. The resulting design is both simple and reliable; the application framework will handle re-assigning Activity instances as they are torn down and recreated, and the TaskFragment and its AsyncTask never need to worry about the unpredictable occurrence of a configuration change.