I have an Android Activity, from which I want to show a Dialog. It would probably be a custom DialogFragment. Now when the user clicks on specific buttons I want the layout's inside the dialog to change with the data from the previous DialogFragment and so that it would have an ability to also go back to previous Layout.
I dont think there is an easy way to change views inside of the same DialogFragment so what would be the best way to do this?
I have tried doing it in method onViewCreated and when a button is clicked, but nothing happens.
In my activity I call the fragment like this at the moment:
FragmentManager fm = getSupportFragmentManager();
NewDialog newDialog = NewDialog.newInstace(userId, loc, currentId);
newDialog.setNewClickListener(new NewDialog.OnNewClickListener() {
#Override
public void onCancelClicked() {
finishAdd();
}
#Override
public void onAcceptClicked() {
...
}
});
newDialog.show(fm, "new_frag");
And the fragment:
public class NewDeliveryPointDialog extends DialogFragment {
private LayoutInflater inflater;
private ViewGroup container;
public NewDialog(){
}
public static NewDialog newInstace(){
...
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
this.inflater = inflater;
this.container = container;
return inflater.inflate(R.layout.dialog_layout_1, container);
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
saveButton.setOnClickListener(v -> {
View.inflate(getContext(), R.layout.dialog_layout_2, container);
view.invalidate();
view.refreshDrawableState();
}
});
}
}
A DialogFragment is not made to have navigation to other fragments within the same dialog.
You basically have these options:
On your button click you close the Dialog and open another Dialog. But this seems odd. If there is so much happening, probably dialogs are not the best shot.
Instead of DialogFragments have another fragment container overlaying the original one (basically what a Dialog fragment does for you). Within the second container you can easily navigate to other fragments and set it to gone when the user finished interaction.
If there are just a few Views in the Dialog, you could consider setting the old ones to gone and the new ones to visible
I think your code didn't work, because container is null. Method onCreateView gives you #Nullable ViewGroup container, which is null for DialogFragment (but non null for Fragment). So when you call View.inflate(getContext(), R.layout.dialog_layout_2, container), it just creates a view in memory and doesn't attach it to container, cause it is null. See LayoutInflater.inflate, cause View.inflate is just a convenience wrapper for this function.
I dont think there is an easy way to change views inside of the same DialogFragment so what would be the best way to do this?
Instead of changing dialog root you can just manipulate child views inside dialog root layout (add, remove them, or change visibility).
Also my advice is to use recommended way to create dialog with custom layout (onCreateDialog + setView), but if you don't want to do that, you can refer view you've created in onCreateView as dialog root.
You can try creating a dialog fragment with an empty shell layout in which you would replace your two different fragments with ChildFragmentManager and regular fragment transactions
passing data between them can be done using the activity's view model since they both live in the same activity.
So add the ShellDialogFragment using the activity's FragmentManager and in the shell fragment class change between NewDialog & NewDeliveryPointDialog on your button click listener with ChildFragmentManager
Related
Let's say I've got an Activity with only a FrameLayout (I've also try with the new FragmentContainerView) so I will be loading Fragments on it. Let's assume I've got two Fragments: fragA and fragB, I will first load fragA and with a button, present in that fragment, replace it with fragB.
I've define a function on my Activity so I can load a new Fragment on the container:
public void loadFragment(Fragment fragment) {
FragmentTransaction fragTrans = getSupportFragmentManager()
.beginTransaction()
.replace(R.id.container, fragment)
.addToBackStack(null);
fragTrans.commit();
}
And I will call this from fragA to go to fragB. This far so good, everything works fine. Actually the problem I'm facing is not about functioning, is that I don't like the loading time of fragB, it takes about 2sec, and I'd like to display a loading to the user, but I'm not able to achieve it.
The first thing I've try is adding a gone ProgressBar to the activity layout, where the container is. Then my loadFragment function will first set this progressBar visible and then perform the fragment transaction and each fragment will hide (gone) this View before exiting the onCreateView method. But the problem is that the user never sees this ProgressBar, it's like the main UI is freezed while rendering the layout of the fragB and therefore does not update the UI making this progressBar usesless. I even see some frame skipped in the LogCat about 50 frames.
Both fragments have no logic just the onCreateView implemented where the layout is inflated. I can notice that if I change the layout of fragB to a simpler one it loads instantly, so I'm guessing the issue is related to the time it takes to render the layout. This heavy layout is a really big form with lots of TextInputLayout with TextInputEditText and MaterialSpinner (from GitHub).
I know I maybe can simplify the layout, but I'd like to know how can I display a loading while rendering. For example, I've seen some apps load some kind of dummy-view while loading and then replace it with real data.
One thing I've try is to load a dummy layout with a ProgressBar in the middle and in the onCreateView method post a Handler to inflate a real layout on the same container in the background, like this:
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_dummy, container, false);
Handler mHandler = new Handler();
mHandler.post(new Runnable() {
#Override
public void run() {
container.removeAllViews();
View realView = inflater.inflate(R.layout.fragment_real, container);
}
});
}
This kinda work, as the viewing experience is nice, but when navigating back, the layout of fragB remains visible as background of the other fragments. I have not test it but maybe I can call container.removeAllViews(); before exiting the fragment and it will work, but still seems like a workaround rather than a solution, to me.
And other thing I've not try because maybe is an over-kill is to have an intermediate or loading fragment and load it always before the real fragment, and I will pass an Intent Extra to it so I can tell what's the real fragment I want to display. This intermediate fragment will not be added to the backstack.
How do you solve this kind of problems?
Well it seems that AsyncLayoutInflater is the way to go!
Here's how I use it:
private ViewGroup fragmentContainer;
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View dummyView = inflater.inflate(R.layout.fragment_dummy_loading, container, false);
fragmentContainer = container;
AsyncLayoutInflater asyncLayoutInflater = new AsyncLayoutInflater(getContext());
asyncLayoutInflater.inflate(R.layout.fragment_real_layout, container, new AsyncLayoutInflater.OnInflateFinishedListener() {
#Override
public void onInflateFinished(#NonNull View view, int resid, #Nullable ViewGroup parent) {
fragmentContainer.removeAllViews();
fragmentContainer.addView(view);
}
});
return dummyView;
}
#Override
public void onStop() {
super.onStop();
fragmentContainer.removeAllViews();
}
Up there, fragment_dummy_loading is a layout with a ProgressBar and fragment_real_layout is the real heavy layout.
There's just one thing I don't get... and that's how can I bind Objects to the XML widgets (without using Android Data Binding, if possible) when the AsyncLayoutInflater fallsback to infalte in the UI thread.
According to the docs:
If the layout that is trying to be inflated cannot be constructed asynchronously for whatever reason, AsyncLayoutInflater will automatically fall back to inflating on the UI thread.
And I check the source of AsyncLayoutInflater and I can tell that the method onInflateFinished is called no matter if the inflation was in the background or in the main thread
I think you have to use AsycTask when your new fragment loading
1- Load the new fragment
2 -Start Async task
3 - Initialize Progressdialog in constructor of AsyncTask where you want to load the data
4 - Show Progressdialog in onPreExecute() in your AsyncTask with dialog.show()
5 - Dismiss the Progressdialog in onPostExecute() in your AsyncTask with dialog.dismiss()
6- Update the UI / set the new data to your adapter etc
Check here
Show a loading spinner while a fragment is loading
I am using fragments to design my screen.When I navigate back to another fragment (from the back stack), the onCreateView(...) method gets called each time even if the fragment has already been created.How to avoid that the method onCreateView(...) gets called each time and make sure it's called only once (when it's created the first time)?
You can cache your inflated view to the local field if your want. For example:
public class ExampleFragment extends Fragment {
private View fragmentView;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
if (fragmentView == null) {
fragmentView = inflater.inflate(R.layout.you_super_view_id, container);
}
return fragmentView;
}
}
But practically, it's ok that pager is reinflating views because it keeps only part of all fragments in memory at the time. So, I think the best idea is to let it work as it should
I am looking for a hint/way how to achieve such a "layout" in android. What I have so far is layout of mainActivity, on which is embedded fragment A. What I wanna do, is place above fragment A next one (Fragment B). For better illustration image is included.
How should I implement such a composition ? Also, after click on "Save" it should process inserted information a fragment should be hidden/removed.
Thanks in advance
You can use DialogFragment. You may refer to this or this tutorial
Create layout (your editview and button), e.g. fragment_register_account.xml
Inflate the layout in a class e.g. RegisterAccountDialog that extend DialogFragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_register_account, container);
mEditText = (EditText) view.findViewById(R.id.txt_your_name);
getDialog().setTitle("Hello");
return view;
}
3.Register onClick to your 'Create new Account' button and show the DialogFragment.
FragmentManager fm = getSupportFragmentManager();
RegisterAccountDialog dialog = new RegisterAccountDialog();
dialog.show(fm, "fragment_edit_name");
A good tutorial here
I have a dialog fragment using a custom layout with a quite complex View hierarchy. The code for the dialog fragment is more or less similar to the following.
public class CardDetailDialog extends DialogFragment {
public CardDetailDialog() {
setRetainInstance(true);
setStyle(STYLE_NORMAL, android.R.style.Theme_Light);
}
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.card_detail_dialog, container, false);
/* Modify some view objects ... */
return view;
}
}
Whenever I invoked the show() method for this dialog fragment, I noticed that onCreateView is always called and layout inflation process is repeated. In my app, user might want to show the dialog multiple times during a session and I thought this is inefficient. Is there any way to keep the view / dialog instance across multiple show() invocation? Is it possible to do this using DialogFragment, or do I have to deal directly with Dialog class?
Using a boolean flag seems to do the trick (See the KEY CHANGEs). I override onCreateDialog, but employing the same strategy in onCreateView should work as well (keep a reference to your view you create)
I'm still getting some issues related to Orientation changes, but it may be related to a different issue
public class LogFragment extends DialogFragment{
private boolean isCreated; //KEY CHANGE
private Dialog mDialog; //KEY CHANGE -- to hold onto dialog instance across show()s
public LogFragment() {
setRetainInstance(true); // This keeps the fields across activity lifecycle
isCreated = false; // KEY CHANGE - we create the dialog/view the 1st time
}
#Override
public Dialog onCreateDialog(Bundle inState) {
if (isCreated) return mDialog; // KEY CHANGE - don't recreate, just send it back
View v = View.inflate(getActivity(),R.layout.log_layout,null);
mDialog = new AlertDialog.Builder(getActivity())
...
.create();
isCreated = true; // KEY CHANGE Set the FLAG
return mDialog;
}
I am using a DialogFragment, and while I have successfully set an image to close (i.e. dismiss) the dialog when pressed, I am having a hard time finding the way to dismiss the dialog when the user clicks anywhere outside it, just as it works with normal dialogs. I thought there would be some sort of
dialogFragment.setCanceledOnTouchOutside(true);
call, but I don't see that in the documentation.
Is this possible with DialogFragment at all? Or am I looking in the wrong places? I tried intercepting touch events in the 'parent' activity but apart from not getting any touch event, it didn't seem right to me.
DialogFragment.getDialog().setCanceledOnTouchOutside(true);
Must be called in onCreateView (as Apurv Gupta pointed out).
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
...
getDialog().setCanceledOnTouchOutside(true);
...
}
/** The system calls this only when creating the layout in a dialog. */
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// The only reason you might override this method when using onCreateView() is
// to modify any dialog characteristics. For example, the dialog includes a
// title by default, but your custom layout might not need it. So here you can
// remove the dialog title, but you must call the superclass to get the Dialog.
Dialog dialog = super.onCreateDialog(savedInstanceState);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setCanceledOnTouchOutside(true);
return dialog;
}
Lot of answers here but, the app crash when dialog opens.
Writing getDialog().setCanceledOnTouchOutside(true); inside onCreateView did not work and crashed my app.
(I am using AppCompatActivity as my BaseActivity and android.app.DialogFragment as my Fragment).
What works is either of the two following lines:
getDialog().setCanceledOnTouchOutside(true);
OR
this.getDialog().setCanceledOnTouchOutside(true);
inside onActivityCreated like
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//getDialog().getWindow().getAttributes().windowAnimations = R.style.DialogAnimationZoom;
//getDialog().getWindow().setDimAmount(0.85f);
getDialog().setCanceledOnTouchOutside(true);//See here is the code
}
What not to use:
DialogFragment.getDialog().setCanceledOnTouchOutside(false);
throws following error
And writing the code in onCreateView crashes the App!
Please update the answer if you find something wrong.
If you want to execute some logic when clicking outside of a DialogFragment, just override the onCancel method.
override fun onCancel(dialog: DialogInterface) {
super.onCancel(dialog)
// Do your work here
}
This works fine for Java:
DialogFragment.getDialog().setCanceledOnTouchOutside(false);
I would recommend to use my solution only after trying out above solutions. I have described my solution here. Just to brief, I am checking touch bounds of DialogFragment.getView(). When touch points are outside DialogFragment, I am dismissing the Dialog.
Dialog.SetCanceledOnTouchOutside(true);
Worked for me
My Code
class dlgRegister : DialogFragment
{
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
....
....
}
public override void OnActivityCreated(Bundle savedInstanceState)
{
Dialog.Window.RequestFeature(WindowFeatures.NoTitle);
Dialog.SetCanceledOnTouchOutside(true);
base.OnActivityCreated(savedInstanceState);
Dialog.Window.Attributes.WindowAnimations = Resource.Style.dialog_animation;
}
}
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup
container, #Nullable Bundle savedInstanceState)
{
DialogConfermationdialogBinding binding = DialogConfermationdialogBinding.inflate(inflater,container,false);
View view = binding.getRoot();
getDialog().setCanceledOnTouchOutside(false);
return view;
}