I have a fully working app which contains a fragment declared as follows:
public class SearchableListFragment extends Fragment implements TabListener
At one point during the work of the fragment, we execute a rather slow loop. Imagine something like:
for(int i = 0;i < large_number;i++)
{
// do complex maths
}
but this loop takes rather a long time and I'd like to have a progress bar appear during the loop. I have seen a variety of examples of adding dialogs, but they all seem to fail because one part or another appears not to apply to fragments.... or they are not applicable because they assume one thing or another that simply does not apply in my case. How can I wrap my loop in some code that will display a progress bar (either a linear bar or a swirling circle - whatever's easiest).
EDIT: The fragment is deployed within a SherlockFragmentActivity. Also the fragments are being implemented via android.support.v4.app.*
You might want to take a look at this question: addView not visible in custom FrameLayout but clickable
Especially its "SOLUTION" ( http://pastebin.com/2xjnCDLS ), which is a viewgroup which switches between the progressbar and the contentview(s), and is easily usable in your layout-xml, was very helpful for me in various occasions. Just call showProgressbar() before and showNext() after you've loaded your content/made your computations.
I have this ModalProgress class that I use every where to show modal progress, I guess you can use it as it is:
public class ModalProgress extends DialogFragment {
public static void show(Activity activity, String title,
String message) {
if (activity == null) {
return;
}
FragmentManager fm = activity.getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
hideInternal(ft, fm);
// Create and show the dialog.
DialogFragment newFragment = new ModalProgress();
Bundle args = new Bundle();
args.putString("title", title);
args.putString("message", message);
newFragment.setArguments(args);
newFragment.show(ft, "dialog");
}
private static void hideInternal(FragmentTransaction ft, FragmentManager fm) {
Fragment prev = fm.findFragmentByTag("dialog");
if (prev != null) {
ft.remove(prev);
}
}
public static void hide(FragmentActivity activity) {
if (activity == null) {
return;
}
FragmentManager fm = activity.getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
hideInternal(ft, fm);
ft.commit();
}
public ModalProgress() {
setCancelable(false);
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Bundle args = getArguments();
ProgressDialog dialog = new ProgressDialog(getActivity());
String title = args.getString("title");
String msg = args.getString("message");
if (!TextUtils.isEmpty(title)) {
dialog.setTitle(title);
}
if (!TextUtils.isEmpty(msg)) {
dialog.setMessage(msg);
}
return dialog;
}
}
Now move your loop code inside AsyncTask:
private class LongTask extends AsyncTask<Void, Void, Void> {
protected void onPreExecute (){
ModalProgress.show(getActivity(), "Working", "Please wait...");
}
protected Void doInBackground(Void... voids) {
for (int i = 0; i < count; i++) {
// your loop here
}
return null;
}
protected void onPostExecute(Void result) {
ModalProgress.hide(getActivity());
}
}
and then just execute AsyncTask.
Related
I have a NagivationDrawer, when the user presses a specific item I change the fragment.
So I have something like this
public class MyFragment extends Fragment {
#Override
public void onCreate(Bundle savedInstanceState) {
......
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState){
.......
}
public class MyAsynchTack extends AsyncTask<Void, Void, Boolean> {
.
.
.
#Override
protected void onPostExecute(final Boolean success) {
showProgress(false);
if (success) {
getActivity().getSupportFragmentManager().popBackStack();
} else {
String title = getString(R.string.serverErrTitle);
String message = getString(R.string.serverErr);
DialogBoxNetworkError dialog = new DialogBoxNetworkError();
Bundle args = new Bundle();
args.putString(DialogBoxNetworkError.ARG_TITLE, title);
args.putString(DialogBoxNetworkError.ARG_MESSAGE, message);
dialog.setArguments(args);
dialog.show(getActivity().getFragmentManager(), "tag");
}
}
.
.
.
.
}
I am using this to remove the fragment on success :
if (success) {
getActivity().getSupportFragmentManager().popBackStack();
}
But it's not working, I have tried other solutions I found as well.
Thank you.
Edited
this is how the fragment is added, from NavigationDrawer activity :
public void addNewWh(View view) {
SellerNewWhFragment fragment = null;
fragment = new SellerNewWhFragment();
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.frame, fragment);
fragmentTransaction.commit();
}
You missed the fragmentTransaction.addToBackStack(null); call
as the documentation states:
Add this transaction to the back stack. This means that the transaction will be remembered after it is committed, and will reverse its operation when later popped off the stack.
After that you will be able to reverse this transaction.
I tried to create a ProgressFragment to be used in the whole application:
public class ProgressFragment extends Fragment {
private static final String KEY = "info";
private CustomProgressView mProgress;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(...);
mProgress = (CustomProgressView) root.findViewById(...);
mProgress.start();
if (getArguments() != null) {
mProgress.setText(getArguments().getString(KEY, ""));
}
return root;
}
public void setInformation(String text) {
if (mProgress != null) {
if (this.isHidden()) {
show(getActivity().getSupportFragmentManager());
}
mProgress.setText(text);
} else {
Bundle b = new Bundle();
b.putString(KEY, text);
setArguments(b);
}
}
public void hide(FragmentManager fm) {
FragmentTransaction ft = fm.beginTransaction();
ft.hide(this);
ft.commit();
}
private void show(FragmentManager fm) {
FragmentTransaction ft = fm.beginTransaction();
ft.show(this);
ft.commit();
}
}
The ProgressFragment is expected to attached(added) to the activity once, and there should be only one instance, then the client should only update its' information or hide it.
Usage in client(activity):
class MainActivity...{
ProgressFragment mFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mFragment = ProgressFragment.newInstance(null);
FragmentTransaction fs = getSupportFragmentManager().beginTransaction();
fs.add(id, mFragment);
fs.commit();
}
//close the progress
mFragment.hide()
//show the progress
mFragment.setInformation("..")
}
However it does not worked as expected, sometime the progress view will show only at the first call or it does not even show.
Did I miss anything?
Don't set your onCreateView as #Nullable;
Use commitAllowingStateLoss();
Instead of just add(), use addToBackStack(). When you want to close it, just use the FragmentManager popBackStack():
getActivity().getSupportFragmentManager().popBackStack();
I am reasonably new to fragments, so I apologise if this is quite basic.
My app contains a ViewPager to swipe between several different tabs with ListViews. On clicking one particular ListView item, I would like to open a DialogFragment displaying some options. My intention is to have the DialogFragment deal with the click event and feed the selected dialog item back to the fragment that called it. I am doing this by providing a callback to MainActivity (which contains the ViewPager) and feeding that information down to the correct ViewPager fragment.
What I have noticed is that my DialogFragment is returning null on getActivity(), which I believe is because my DialogFragment is not correctly attached to my MainActivity. How can I achieve this? I find it a little harder to navigate through this issue considering I am new to callbacks and most other examples dealing with fragments are for standard fragments, not those from ViewPager.
How can my dialog identify the correct activity? Should I be attaching the fragment to a FragmentManager? If so, should it be attached to the same FragmentManager as the ViewPager fragments?
This is my code, though I'm not sure how helpful it will be.
CalibrationFragment.java - Calling the DialogFragment. (Not sure if this is the correct way to do it.)
ModeDialogFragment modeDialog = ModeDialogFragment.newInstance(R.string.mode_calibration);
modeDialog.onCreateDialog(null);
ModeDialogFragment.java - My DialogFragment
public class ModeDialogFragment extends DialogFragment{
public static ModeDialogFragment newInstance(int title) {
ModeDialogFragment frag = new ModeDialogFragment();
Bundle args = new Bundle();
args.putInt("title", title);
frag.setArguments(args);
return frag;
}
public void onAttach(Activity activity){
super.onAttach(activity);
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
int title = getArguments().getInt("title");
final Context mContext = getActivity();
getActivity().getFragmentManager().beginTransaction().add(newInstance(R.string.mode_calibration), "my_fragment").commit();
// Trying to add my dialog to the MainActivity, not working.
final CharSequence[] modeItems = {
"Option 1", "Option 2", "Option 3"
};
final int checkedItem = 2; // Make final for now, resolve this later
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(getResources().getString(R.string.mode_calibration));
builder.setSingleChoiceItems(modeItems, checkedItem, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
// Handle the selection here
((MainActivity)getActivity()).updateModeData();
}
});
return(builder.create());
}
}
MainActivity.java
public class MainActivity extends FragmentActivity implements ActionBar.TabListener {
// Initialise variables
private Context mContext;
// [Others...]
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Initialisation
viewPager = (ViewPager) findViewById(R.id.pager);
actionBar = getActionBar();
mAdapter = new TabsPagerAdapter(getSupportFragmentManager());
}
public Fragment findFragmentByPosition(int position) {
int viewId = R.id.pager;
//FragmentPagerAdapter fragmentPagerAdapter = getFragmentPagerAdapter();
return getSupportFragmentManager().findFragmentByTag(
makeFragmentName(viewId, position));
}
// Needed to identify a fragment from ViewPager
private static String makeFragmentName(int viewId, int position)
{
return "android:switcher:" + viewId + ":" + position;
}
public void updateModeData(){
// Make call to the fragment to deal with the data
CalibrationFragment calFragment = (CalibrationFragment) findFragmentByPosition(3);
((CalibrationFragment) calFragment).doUpdateModeData();
}
}
Any help or guidance would be appreciated!
EDIT: The problem was indeed that I was not adding the fragment to a FragmentManager and calling it correctly. This is the code I used. My code still does not run through in its entirety, but for the purpose of this question, my DialogFragment can now access MainActivity.
CalibrationFragment.java - Calling DialogFragment
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ModeDialogFragment modeDialog = ModeDialogFragment.newInstance(R.string.mode_calibration);
String title = makeFragmentName(R.id.pager, 3); // makeFragmentName as specified in MainActivity.java
modeDialog.show(ft, title);
ModeDialogFragment.java is the same as before, but with the following line removed:
getActivity().getFragmentManager().beginTransaction().add(newInstance(R.string.mode_calibration), title).commit();
You shouldn't call onCreateDialog by yourself. Just call show method with FragmentTransaction:
FragmentTransaction ft = getFragmentManager().beginTransaction();
ModeDialogFragment modeDialog = ModeDialogFragment.newInstance(R.string.mode_calibration);
modeDialog.show(ft, null);
Right usage of DialogFragment is described here: http://developer.android.com/reference/android/app/DialogFragment.html
An SSCCE for this issue is available on GitHub.
For future readers, the original example is on a branch of the same project, and the fix is available in this diff.
This SSCCE has a ListView and a row of buttons. The buttons are supposed to change the data in the ListView, and the listView rows (when clicked) are supposed to open a new fragment and advance the backstack while staying in the same activity.
If do the following things, it produces the following result:
Open the app.
Tap the ListView. - FragmentTransaction.replace(...) with addToBackStack(true)
Tap any of the buttons. - FragmentTransaction.replace(...) with addToBackStack(false)
Tap the back button.
Result:
Both fragments become visible, but I only want the first loaded fragment (ListTwoFragment in code) to display. Is this how fragments are supposed to work? If so, how can I get the desired effect?
MainActivity.java:
public class MainActivity extends FragmentActivity implements ListTwoFragment.Callbacks,
ListThreeFragment.Callbacks {
public static final String KEY_ARGS = "args";
private String curUri = "";
private String curArgs = "";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
selectContent(false);
}
private void selectContent(boolean addToBackStack) {
Fragment fragment;
if (curUri.isEmpty()) {
// Use default fragment
fragment = new ListTwoFragment();
curUri = ListTwoFragment.class.getName();
}
else {
try {
Class<Fragment> fragmentClass = (Class<Fragment>) Class.forName(curUri);
fragment = fragmentClass.newInstance();
}
catch (Exception e) { // ClassNotFound, IllegalAccess, etc.
return;
}
}
// Configure fragment
Bundle args = new Bundle();
args.putString(KEY_ARGS, curArgs);
fragment.setArguments(args);
attachFragment(fragment, addToBackStack, curUri + ";" + curArgs, R.id.fragment_container);
}
protected void attachFragment(Fragment fragment, boolean addToBackStack, String tag, int replaceId) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(replaceId, fragment, tag);
if (addToBackStack) transaction.addToBackStack(tag);
transaction.commit();
}
#Override
public void onTwoButtonClick(String title) {
curUri = ListTwoFragment.class.getName();
curArgs = title;
selectContent(false);
}
#Override
public void onTwoListClick() {
curUri = ListThreeFragment.class.getName();
curArgs = "";
selectContent(true);
}
#Override
public void onThreeButtonClick(String title) {
curUri = ListThreeFragment.class.getName();
curArgs = title;
selectContent(false);
}
}
I'm working with Fragments to, and the way I'm doing it:
to go forward (add to stack), and backwords (remove from stack) are two different functions
to Add to Stack and change Fragment:
public void changeFragmentAddToStack(Fragment myNewFragment) {
FragmentTransaction t = getSupportFragmentManager().beginTransaction();
t.add(R.id.main_fragment, myNewFragment);
t.addToBackStack(null);
t.commit();
}
To go back Stack:
public void goBackStackMain() {
FragmentManager man = getSupportFragmentManager();
if(man.getBackStackEntryCount()>0){
man.popBackStack(man.getBackStackEntryAt(0).getName(), FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
}
And if you want to do both: To go back stack and change fragment:
public void goBackStackAndReplaceFragment(Fragment myNewFragment) {
FragmentManager man = getSupportFragmentManager();
if(man.getBackStackEntryCount()>0){
int n = man.getBackStackEntryCount();
man.popBackStack(man.getBackStackEntryAt(n-1).getName(), FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
FragmentTransaction t = getSupportFragmentManager().beginTransaction();
t.replace(R.id.main_fragment, myNewFragment);
t.commit();
}
I hope to help you !
I created a progressdialog following the new "fragment way" with this code:
public class DialogUpdateTrackRecords extends DialogFragment {
public static DialogUpdateTrackRecords newInstance() {
DialogUpdateTrackRecords frag = new DialogUpdateTrackRecords();
return frag;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public ProgressDialog onCreateDialog(Bundle savedInstanceState) {
this.setCancelable(false);
ProgressDialog dialog= new ProgressDialog(getActivity());
dialog.setTitle("Caricamento tragitti");
dialog.setIndeterminate(true);
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setMessage("Sending something");
return dialog;
}
}
and I show it from an activity like this way:
FragmentManager fm= getSupportFragmentManager();
uploadDialogFrament= (DialogFragment) getSupportFragmentManager().findFragmentByTag("sendDialog");
if(uploadDialogFrament!=null)
uploadDialogFrament.dismiss();
FragmentTransaction ft= fm.beginTransaction();
uploadDialogFrament= DialogUpdateTrackRecords.newInstance();
uploadDialogFrament.show(ft,"sendDialog");
fm.executePendingTransactions();
((ProgressDialog)uploadDialogFrament.getDialog()).setMax(trackRecordSize);
if( trackRecordSize > 1 )
((ProgressDialog)uploadDialogFrament.getDialog()).setIndeterminate(false);
As you can see, I get a reference to the dialog and I set it (depending on my needs). Everything works like a charm but.. if I rotate the device, the dialog goes back to its pristine state instead of retaining (in the example: the bar is set back to an indefinite state) my new settings. I checked if I was creating and displaying a new dialog erroneously, but this is not the case. So.. how could I keep my changes over the recreation of the activity?
Have you tried
setRetainInstance(true)
Since the DialogFragment extends the base Fragment, im pretty sure that will work.
OK.. this is simply a workaround and I DO NOT SUGGEST IT. Notwithstanding the displaimer: I really don't have the necessary time to dig into the logic of this bug and I used this simple workaround. To make it short: each time I resume the dialog fragment, I update it with all the settings it should automatically have. Here's an example:
public class DialogUpdateTrackRecords extends DialogFragment {
private boolean indeterminate= true;
public static DialogUpdateTrackRecords newInstance() {
DialogUpdateTrackRecords frag = new DialogUpdateTrackRecords();
return frag;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public void onResume() {
super.onResume();
((ProgressDialog)getDialog()).setIndeterminate(indeterminate);
}
#Override
public ProgressDialog onCreateDialog(Bundle savedInstanceState) {
this.setCancelable(false);
ProgressDialog dialog= new ProgressDialog(getActivity());
dialog.setTitle("Caricamento tragitti");
dialog.setIndeterminate(true);
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setMessage("Invio delle statistiche completate a ISF Modena in corso");
return dialog;
}
public void setIndeterminate(){
((ProgressDialog)getDialog()).setIndeterminate(false);
indeterminate= false;
}
/*
[italian soh]
http://stackoverflow.com/questions/12433397/android-dialogfragment-disappears-after-orientation-change
thanks, google. Thanks.
[/italian soh]
*/
#Override
public void onDestroyView() {
if (getDialog() != null && getRetainInstance())
getDialog().setDismissMessage(null);
super.onDestroyView();
}
}
Anyway: if anybody finds the reason of that weird behavior I would be more than happy to fix my code properly.