i am trying to reload ListFragment from AsyncTask like this :
//set the string of the kitchen banner depending on the
#Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
RequestNewTopBannerFragment requestNewTopBannerFragment = new RequestNewTopBannerFragment();
Bundle argsBanner = new Bundle();
argsBanner.putString(RequestNewTopBannerFragment.ARG_KEY_BANNER_TEXT_STR, crrentTopBannerString);//this.currentRequestType ContractFoodServices.NEW_REQUEST_CONST;
requestNewTopBannerFragment.setArguments(argsBanner);
transaction.replace(R.id.fragment_container_top_banner_kitchen, requestNewTopBannerFragment) ;
transaction.addToBackStack(null);
transaction.commit();//LIGHT TIGER : commitAllowingStateLoss()
}
but although i am doing this in function "onProgressUpdate" but i have get exception "IllegalStateException: Can not perform this action after onSaveInstanceState"
of course if i change the function "commit" to "commitAllowingStateLoss" it shows the same exception , and at the same time , the Fragment is not reloaded all the times , some times it works and others not.
i tried to use another way to reload the ListFragment from thread so i tried this code :
runOnUiThread(new Runnable() {
public void run() {
RequestNewTopBannerFragment requestNewTopBannerFragment = new RequestNewTopBannerFragment();
Bundle argsBanner = new Bundle();
argsBanner.putString(RequestNewTopBannerFragment.ARG_KEY_BANNER_TEXT_STR, crrentTopBannerString);//this.currentRequestType ContractFoodServices.NEW_REQUEST_CONST;
requestNewTopBannerFragment.setArguments(argsBanner);
transaction.replace(R.id.fragment_container_top_banner_kitchen, requestNewTopBannerFragment) ;
transaction.addToBackStack(null);
transaction.commit();//LIGHT TIGER : commitAllowingStateLoss()
}
});
but it did not work too , and show the same exception , so what is the solution to be able to reload the FragmentList at all time from "AsyncTask"
thanks in advance
Related
I have a simple Fragment with this in the onViewCreated method:
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (account != null) {
try {
Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(() ->
{
String decryptedCode;
try {
decryptedCode = (vaultService).getDecryptedPassword(account).trim();
in_password.setText(decryptedCode);
} catch (Exception e) {
Helper.showMessage(e.toString());
} finally {
in_password_layout.setHelperText("");
}
}, 1);
} catch (Exception e) {
Helper.showMessage(e.toString());
}
}
}
and calling the Fagment i have:
protected void openFragment(BaseFragment fragment) {
fragment.setCaller(this);
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.setCustomAnimations(R.anim.activity_slide_in_right, R.anim.activity_slide_out_left, R.anim.activity_slide_out_right, R.anim.activity_slide_in_left);
fragmentTransaction.replace(R.id.content_frame, fragment);
fragmentTransaction.addToBackStack(fragment.getClass().getName());
fragmentTransaction.commit();
}
The problem that i am facing is that if i remove the Handler call from the onViewCreated method, the transition occurs perfectly. If i put the Handler, like so, it kills the animation and just shows the fragment without any animation.
If i use the handler.postInFrontOfQueue the animation works, but takes a while before coming in. Which means it processes the Handler first and only then executes the animation and transits to the Fragment.
Do you have any idea how can i prevent this? I already tried in an independent Thread and it does not work.
Thanks.
I just found a solution. I have to put the amount of time it takes for the animation to occur in the 2nd parameter of the postDelayed method.
In my case:
getResources().getInteger(android.R.integer.config_mediumAnimTime)
So it only triggers the handler after the animation finishes.
Not exactly what i wanted but it works. I would rather have it both process in parallel so that when the animation finishes the Handler is also done with its work.
I think I know why this happens.
When rotating the screen the Activity gets destroyed and recreated and the runOnUiThread() method is referencing the old(destroyed) Activity which isn't valid anymore and so android throws a runtimeexception.
To demonstrate this I have this code:
#ContentView(R.layout.activity_start)
public class StartActivity extends RoboSherlockFragmentActivity {
#Inject
private EventManager eventManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
Fragment newFragment = new WelcomeFragment();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.fragment_container, newFragment).commit();
new Thread(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(2000);
//Do some short initialization here like read shared prefs
//and then decide for example whether to login or skip the login
//If the rotation happens while sleeping the app will certainly crash
runOnUiThread(new Runnable() {
#Override
public void run() {
addFragmentToStack();
}
});
} catch (Exception e) {
// TODO: handle exception
}
}
}).start();
}
}
void addFragmentToStack() {
Fragment newFragment = new LoginOrRegisterFragment();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.fragment_container, newFragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.commit();
}
}
Do I have to use an asynctask for some easy task like that? If so how I handle the rotation then? Because the callback reference would be faulty either.
try this
new Handler(getMainLooper()).postDelayed(new Runnable(){
if(!isFinishing()){
addFragmentToStack();
}
},200);
instead of your thread code
isFinishing() is called when the activity is in the process of being destoryed
It seems that all you're trying to do is to make the task executed after 200 ms have passed.
there is no need to open a new thread for that, a handler will do
2ndly if you want to ensure that the code will be executed on the main thread
you create the handler calling for the main looper
Handler(getMainLooper()) and it will make this handler execute its task on the main thread
AsyncTask will generate the same crash. You can Simply override onPause() and find a way to notify the thread that Activity is about to be destroyed. Like checking for null value of a variable.
public void run() {
if(someVariable != null)
addFragmentToStack();
}
Or you can use a callback fragment (without view) to keep track of configuration change. This article is a great way to start. This method I prefer because it is a very clean approach.
I had a similar problem and solved it by using a retain fragment where I store a reference to the Activity on every OnCreate e.g. retainFragment.activity = this; . In runOnUiThread I check if the retained reference is the current Activity e.g. if (retainFragment.activity == MainActivity.this)..
I've got a main Activity, an extra class for my fragment, and inside this fragment is an AsyncTask, which gathers data from various android library (Wifi SSID, BSSID, etc). When I start my app the app shows a blank screen, without any UI. Then after about 2 seconds, the whole data is being shown. I actually want to display my TextViews as "Not connected to a wifi network" in the background, while showing a ProgressDialog until the data is being displayed. I've got the ProgressDialog in my MainActivity, and calling it in my AsyncTask onProgressUpdate
MainActivity.progressDialog = ProgressDialog.show(MainActivity.c,
"ProgressDialog Title",
"ProgressDialog Body");
I'm updating my TextViews in the doInBackground methode (via another methode outside the Fragment)
((Activity) getActivity()).runOnUiThread(new Runnable() {
Would be too big a comment so i'll just put it here.
Sounds like you are using both fragment and AsyncTask in an incorrect way. You should never do anything UI relevant in doInBackground.
Here is an example of what you could do.
I assume the following scenario:
You have a main activity
You have a fragment containing TextViews
You wish to populate the TextViews after loading some data using AsyncTask with a progressDialog
The approach would be to:
Add the fragment in onCreate of your activity (if the fragment is not defined in the layout, then it will automatically be added).
Create the AsyncTask in your fragment like this:
private class LoadData extends AsyncTask<Void, Void, List<String>> {
ProgressDialog progressDialog;
//declare other objects as per your need
#Override
protected void onPreExecute()
{
// getActivity() is available in fragments and returns the activity to which it is attached
progressDialog= new ProgressDialog(getActivity());
progressDialog.setTitle("ProgressDialog Title");
progressDialog.setMessage("ProgressDialog Body");
progressDialog.setIndeterminate(true)
progressDialog.setCancelable(false)
progressDialog.show();
//do initialization of required objects objects here
};
#Override
protected Void doInBackground(Void... params)
{
List<String> results = new ArrayList<String>();
//do loading operation here
//add each of the texts you want to show in results
return results;
}
// onPostExecute runs on UI thread
#Override
protected void onPostExecute(List<String> results )
{
progressDialog.dismiss();
// iterate results and add the text to your TextViews
super.onPostExecute(result);
};
}
Start the AsyncTask in onCreate of your fragment:
#Override
public void onCreate(Bundle savedInstanceState) {
new LoadData().execute();
super.onCreate(savedInstanceState);
}
This way you avoid calling directly back to your activity, which really should not be necessary in your scenario (unless I have misunderstood).
Otherwise please post all the relevant code and layouts.
This line:
I'm updating my TextViews in the doInBackground methode
points to your problem. You need to use the AsyncTask method onProgressUpdate() to publish to the UI thread. You do not call onProgressUpdate() directly, instead you call publishProgress().
Interestingly, I answered a similar question yesterday here: android AsyncTask in foreach
and it includes an example.
Here's what you need to do.
(1) From the place you run the code that gathers data, you should first display the progress dialog. Something like this:
busy = new ProgressDialog (this);
busy.setMessage (getString (R.string.busy));
busy.setIndeterminate (true);
busy.setCancelable (false);
busy.show();
(2) Then you start your data gathering. This must be done in a separate thread (or Runnable). Do something like this:
Thread thread = new Thread ()
{
#Override
public void run()
{
try
{
... gather data ...
Message msg = handler.obtainMessage();
msg.what = LOADING_COMPLETE;
msg.obj = null;
handler.sendMessage (msg);
}
catch (Exception e)
{
Message msg = handler.obtainMessage();
msg.what = LOADING_FAILED;
msg.obj = e.getMessage(); // maybe pass this along to show to the user
handler.sendMessage (msg);
}
// get rid of the progress dialog
busy.dismiss();
busy = null;
}
}
(3) Add a handler to the activity to receive notification when data gathering is complete:
Handler handler = new Handler()
{
#Override
public void handleMessage (Message msg)
{
if (msg.what == LOADING_COMPLETE)
loadingComplete ();
else if (msg.what == LOADING_FAILED)
loadingFailed ((String)msg.obj);
}
};
(4) Implement the handlers:
private void loadingComplete ()
{
...
}
private void loadingFailed (String errorMessage)
{
...
}
That's the essentials.
I know there are a tonne of answers on SO for this but I can't seem to get it working how I'd like. I have a SherlockFragment containing a ListView which is populated by some data retrieving from a file. I've added a refresh button to the Fragment, the idea being that when you press it it retrieves any changes in the file and adds them to the view if necessary. I also added the popular PullToRefresh library.
Unfortunately, nothing changes however when I reload the Fragment (for example, rotating the device) I can see the new data. I've read about notifyDataSetChanged() but it isn't working for me. The only thing I've gotten working is calling mPager.notifyDataSetChanged() from my main FragmentActivity class and having the following set in my ViewPager adapter:
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
This accomplishes what I want, but it's not very sleek about it, and you can see it forcing the reload as it were. I'm currently using PullToRefresh and it forces it off page abruptly and quite frankly it just looks bad. To put it bluntly I need to call onCreateView of my Fragment from my AsyncTask reloading the data.
If needed I'll post code, there's just a lot of it so I wouldn't want to post anything unneeded.
Oh, and please note I tried notifyDataSetChanged() in the onPostExecute() of my task.
Thanks for any guidance.
I found a way around this but it's not perfect, so I won't accept this in case something better comes along.
Because upon return to my Fragment onResume() is called (this answer is also pretty specific to my project), I just did the following:
#Override
public void onResume(){
super.onResume();
// Make the adapter again
adapter = new FeedListAdapter(this, feed);
// Set it to the list again
list.setAdapter(adapter);
}
This refreshes the list (badly) and is still fairly noticeable, although if I use a button instead of PullToRefresh it isn't.
My solution (however bad) alongside PullToRefresh is to stick it in a handler with a delayed trigger to let the pulled down "refresh" section disappear before it runs.
#Override
public void onResume(){
super.onResume();
new Handler().postDelayed(new Runnable() {
public void run() {
adapter = new FeedListAdapter(this, feed);
list.setAdapter(adapter);
}
}, 500);
}
Again, this is pretty specific to my project and a very strange way of doing it, so any "perfect" answer please share :)
EDIT: There was still an issue with it being a bit jumpy with PullToRefresh, so my solution was to wait in a new thread until the PullToRefresh is hidden again, then rebuild the list (it's messy, but it works so whatever):
#Override
public void onResume(){
super.onResume();
// Start a new thread
new Thread(new Runnable() {
#Override
public void run() {
try {
// Wait until the PullToRefresh is hidden
synchronized(this){
wait(190);
}
} catch(InterruptedException ex) { }
// Post a new runnable
threadHandler.post(new Runnable(){
public void run(){
// Recreate the adapter from the new feed
adapter = new FeedListAdapter(FeedListActivity.this, feed);
// Set the recreated adapter
list.setAdapter(adapter);
}
});
}
}).start();
}
}
EDIT 2:
Just noticed I missed something obvious. I just changed my list.onRefreshComplete(); for the PullToRefresh view to my onResume() and the jumpiness was taken care of. Still, I think the above solution is more impressive :p
So my code:
#Override
public void onResume(){
super.onResume();
// Recreate the adapter from the new feed
adapter = new FeedListAdapter(this, feed);
// Set the recreated adapter
list.setAdapter(adapter);
// The list has finished refreshing
list.onRefreshComplete();
}
All! I am pretty new to Developing Android. I have run into many issues already and solved most of them myself, and some by searching here and on other sites. The problem I currently face now, I can't seem to find a solution for. It is close to what others on here have asked, but I can't find anything for my problem.
I am working on the beginning stages of my first big app, in which a user signs in to the fist page and is then allowed access to the rest of the app and features with. I am using basic examples right now of a simple log in app and it works fine, but when I try to delay the first activity from calling the second one until the results are posted, either it posts and doesn't call the second activity, or it doesn't post the results and moves on anyways. I am also currently trying to bundle the results and display them in the second activity. I Will change that later though, I just need to see if it will work right.
Here is my code:(not sure if I'm doing this right. It's my first time!)
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Login button clicked
ok = (Button)findViewById(R.id.btn_login);
ok.setOnClickListener(this);
result = (TextView)findViewById(R.id.lbl_result);
final Handler handler1 = new Handler();
handler1.postDelayed(new Runnable() {
#Override
public void run() {
ok.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
final Bundle b = new Bundle();
EditText txt1 = (EditText)findViewById(R.id.editText1);
EditText txt2 = (EditText)findViewById(R.id.lbl_result);
b.putString("ID", txt1.getText().toString());
b.putString("PW", txt2.getText().toString());
final Handler handler2 = new Handler();
handler2.postDelayed(new Runnable() {
#Override
public void run() {
final Intent myIntent = new Intent(TempActivity.this, TempActivity2.class);
myIntent.putExtras(b);
startActivity(myIntent);
}
}, 3000);
}
});
}
}, 4000);
}
Am I just going about this the wrong way? Any help is greatly appreciated! I hate being a noob! Let me know if anymore information is needed!
Ohhhh... i dont know.. if it is actually right.. but as far as i understand your code... you are using
handler1.postDelayed(new Runnable() {
and in its runnable
public void run() {
ok.setOnClickListener(new View.OnClickListener() {
here.. you are setting onClickListener.. and it will happen after 4 seconds.. because of this line..
}, 4000);
so if you click before 4 seconds.. i think onclicklistener is not being set... so give some time.. like 4 seconds after the application starts
.. and then try clicking... i think then it should work..