What I want to achieve
From a FragmentActivity show a dialog when clicking an Action Button in the Action Bar
DialogFragment - A Dialog without title
TabHost - Tabs at the top of the dialog
ViewPager with FragmentPagerAdapter - Swipable, which content is connected to the Tabs
2-3 Dialog Buttons (different subclasses of the Dialog, different buttons) - Are not supposed to be in one of the ViewPager's Fragment, meaning the same buttons should remain at the bottom of the Dialog regardless of what Fragment the ViewPager is showing.
The problem
IllegalStateException: Fragment does not have a view
What I have tried/done so far
Using the android.support.v4 package for necessary classes
Calling getChildFragmentManager() instead of getSupportedFragmentManager()
Implemented what post #10 suggested from this link https://code.google.com/p/android/issues/detail?id=42601. I copy/paste the code directly into my two Fragment classes, which the ViewPager is suppose to be showing, plus the DialogFragment class.
In my custom DialogFragment I first tried to override onCreateView, then onCreateDialog and then both at the same time. All of which I got to run but with unexpected results.
Only onCreateView: Can't reach the AlertDialog.Builder to create the needed buttons, other than that the Dialog's results were great.
Only onCreateDialog: the error message shown above. I still imagine this method to be as close as I've gotten to what I want to achieve.
Both onCreateView and onCreateDialog: Inflated the Dialog layout in onCreateView and added the Dialog buttons to the AlertDialog.Builder in onCreateDialog. This displayed the dialog, but the added buttons from the AlertDialog.Builder were not visable. Plus the keyboard didn't show up when clicking on a EditText field.
Source code
Most come from Tutorial to implement the use of TabHost in Android 2.2 + ViewPager and Fragments. The code of the ActivityFragment is instead in a DialogFragment. However I replaced its ViewPager with a modified one from the source code from this answer https://stackoverflow.com/a/18167273/2375978. This was to be able to wrap_content on height.
The faulty code in my project is in DialogFragment's onCreateDialog method, I believe.
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), AlertDialog.THEME_HOLO_DARK);
LayoutInflater inflater = getActivity().getLayoutInflater();
view = inflater.inflate(R.layout.dialog_test, null);
addActionButtons(builder, view);
builder.setView(view);
mViewPager = (WrapContentHeightViewPager) view.findViewById(R.id.viewpager);
initialiseTabHost();
List<Fragment> fragments = getFragments();
pageAdapter = new DialogPageAdapter(getChildFragmentManager(), fragments);
mViewPager.setAdapter(pageAdapter);
mViewPager.setOnPageChangeListener(this);
Dialog dialog = builder.create();
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
dialog.show();
return dialog;
}
Stack trace LogCat log
FATAL EXCEPTION: main
java.lang.IllegalStateException: Fragment does not have a view
at android.support.v4.app.Fragment$1.findViewById(Fragment.java:1425)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:901)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1088)
at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:682)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1444)
at android.support.v4.app.FragmentManagerImpl.executePendingTransactions(FragmentManager.java:461)
at android.support.v4.app.FragmentPagerAdapter.finishUpdate(FragmentPagerAdapter.java:141)
at android.support.v4.view.ViewPager.populate(ViewPager.java:1011)
at android.support.v4.view.ViewPager.populate(ViewPager.java:880)
at android.support.v4.view.ViewPager.onMeasure(ViewPager.java:1374)
at my.app.package.name.WrapContentHeightViewPager.onMeasure(WrapContentHeightViewPager.java:31)
at android.view.View.measure(View.java:15481)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5059)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1396)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:681)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:574)
at android.view.View.measure(View.java:15481)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5059)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
at android.view.View.measure(View.java:15481)
at android.widget.RelativeLayout.measureChildHorizontal(RelativeLayout.java:617)
at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:399)
at android.view.View.measure(View.java:15481)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5059)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
at android.view.View.measure(View.java:15481)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5059)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
at android.view.View.measure(View.java:15481)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5059)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1396)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:681)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:574)
at android.view.View.measure(View.java:15481)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5059)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
at android.view.View.measure(View.java:15481)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5059)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
at android.view.View.measure(View.java:15481)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5059)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
at com.android.internal.policy.impl.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2377)
at android.view.View.measure(View.java:15481)
at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:1982)
at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1200)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1398)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1118)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:4525)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:725)
at android.view.Choreographer.doCallbacks(Choreographer.java:555)
at android.view.Choreographer.doFrame(Choreographer.java:525)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:711)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4946)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.jav
Also...
I have unsuccessfully been able to try the other possible solution mentioned in https://code.google.com/p/android/issues/detail?id=42601, mentioned in post #2 and #13, because I haven't understood how and where I can use it in my code (I guess I'm in the same boat as the person who wrote #18).
I think I just ran into this same problem and learned a few things by looking at the source for DialogFragment.
It looks like even though overriding onCreateDialog(...) is a valid way to create a custom dialog, it will result in the DialogFragment having a null View, just like the error message says. In most cases this is fine - the DialogFragment doesn't need a View to show a Dialog, but if you want to nest fragments further (like you do), this won't fly.
Considering that you want to interact with an AlertDialog.Builder, there is really no perfect solution that I can see, but you've got a few options:
Create the buttons in your dialog as part of the View (not using AlertDialog.Builder). You do this by overriding onCreateView instead of onCreateDialog. You should be able to get the functionality you mention by putting the buttons in their own fragment. We do something similar at my gig, and I very much prefer this method.
Implement your own type that inherits from Fragment and that mirrors the DialogFragment in every way except allowing what you need. This shouldn't be too scary as DialogFragment is only ~400 and is heavily commented. Could be fun.
Use a regular PagerAdapter instead of a FragmentPagerAdapter. This way it won't matter that your DialogFragment doesn't have a View.
Use the
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
instead of onCreateDialog(Bundle savedInstanceState). Don't create an alert dialog, use the inflater provided by the method, and build your view. It works for me.
Best regards!
If you implement onCreateDialog to use AlertDialog, you will bump into IllegalStateException: Fragment does not have a view when accessing getChildFragmentManager or something equivalent.
To solve this issue, implement both onCreateDialog and onCreateView, where onCreateView return the view inflated in onCreateDialog.
class LocationPickerDialog : DialogFragment() {
lateinit var customView: View
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return customView
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
Log.d(TAG, "onCreateDialog")
// StackOverflowError
// customView = layoutInflater.inflate(R.layout.dialog_location_picker, null)
customView = activity!!.layoutInflater.inflate(R.layout.dialog_location_picker, null)
val builder = AlertDialog.Builder(context!!)
.setView(customView)
.setPositiveButton(android.R.string.ok) { _, _ ->
// do something
}
.setNegativeButton(android.R.string.cancel) { _, _ ->
// do something
}
val dialog = builder.create()
return dialog
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
// if onCreateView doesn't return a view
// java.lang.IllegalStateException: Fragment does not have a view
mapFragment = childFragmentManager.findFragmentByTag("map") as SupportMapFragment?
}
}
https://code.luasoftware.com/tutorials/android/android-alertdialog-in-dialogfragment-fragment-does-not-have-a-view/
Thanks to #Tommy Visic for writing a really good description it worked.
Posting the code which worked for me.
I have removed the Dialog building code from the onCreateDialog method infact removed onCreateDialog method and the dialog view which I have been implementing in the Dialog's custom view I have included it as a View in the onCreateView method and all the things started working.
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.login_sigup_screen, null, false);
bind = ButterKnife.bind(this, view);
initViewPager();
return view;
}
Faced one more problem with this implementation is:
When a Activity has Toolbar/ActionBar then it is also displayed into the DialogFragment to avoid that what is to be done is:
Implement onViewCreated method of Fragment and add below code
Dialog dialog = getDialog();
dialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE);
float dimAmount = 0.6f;
dialog.getWindow().setDimAmount(dimAmount);
This will remove the Toolbar from the DialogFragment and Activity will be displayed as it is.
Cheers
Thanks #Tommy
Regards
Zeus
I ran into same issue and achieve the following solution :
class MyDialogFragment: DialogFragment {
private lateinit var dialog: AlertDialog
private lateinit var dialogView: View
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// return the view inflated for your dialog fragment
return dialogView
}
// this is called before onCreateView
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
dialogView = LayoutInflater
.from(ContextThemeWrapper(requireContext(), R.style.MyAlertDialogStyle))
.inflate(R.layout.fragment_dialog, null, false)
dialog = AlertDialog
.Builder(requireContext())
.setView(dialogView)
.create()
dialog.setOnShowListener {
childFragmentManager
.beginTransaction()
.replace(R.id.container, MyNestedFragmentInsideTheDialog())
.commit()
// retrieve dialog buttons if any to manage onClickListener yourself
// dialog.getButton(AlertDialog.BUTTON_POSITIVE)
}
}
}
The idea is to inflate the dialog content yourself and to keep a reference on it so it can be returned into onCreateView and then used by the childfragmentManager ;-)
you can make variable and put the condition between try and catch same that
val comeFrom = try {
args.comeFrom.toString()
}catch (e:Exception){
"0"
}
Related
I have a DialogFragment with a custom layout. Everything shows properly when the fragment is embedded in my activity using a FragmentTransaction, like so:
getSupportFragmentManager()
.beginTransaction()
.add(
R.id.fragment_container,
exampleDialogFragment,
ExampleDialogFragment.TAG)
.commit();
but when I do:
exampleDialogFragment.show(getSupportFragmentManager(), exampleDialogFragment.TAG);
the dialog only shows the 'Cancel' button. The stuff in the custom layout doesn't appear.
This is what my ExampleDialogFragment class looks like:
public class ExampleDialogFragment extends DialogFragment {
#Override
public View onCreateView(
#NonNull LayoutInflater layoutInflater,
#Nullable ViewGroup viewGroup,
#Nullable Bundle bundle) {
// Inflate layout and init views
}
This documentation https://developer.android.com/guide/topics/ui/dialogs#FullscreenDialog seems to imply that we don't need to override onCreateDialog, so I'm not doing that. However, it still doesn't look right. Am I missing something?
For anyone looking at this, my dialog had an outer ConstraintLayout. Changing it to a RelativeLayout fixed the issue.
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
class BottomBarFragment : BottomSheetDialogFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val contextThemeWrapper = ContextThemeWrapper(getActivity(), R.style.Theme_BaseDarkTheme)
val localInflater = inflater.cloneInContext(contextThemeWrapper)
binding = FragmentBottomBarBinding.inflate(localInflater, container, false)
// tried setStyle also
setStyle(0, R.style.Theme_BaseDarkTheme)
}
}
style
<style name="Theme.BaseDarkTheme" parent="Theme.AppCompat">
<item name="dividerColor">#color/divider_dark</item>
</style>
in fragment dialog layout
<LinearLayout
style="#style/llParent"
android:background="?attr/dividerColor"
>
According to this, my layout should be dark, but it is light always. What am I doing wrong?
The documentation for setStyle() says this:
Call to customize the basic appearance and behavior of the fragment's
dialog. This can be used for some common dialog behaviors, taking care
of selecting flags, theme, and other options for you. The same effect
can be achieve by manually setting Dialog and Window attributes
yourself. Calling this after the fragment's Dialog is created will
have no effect.
Fragment's Dialog will be created after onCreate() and before onCreateView().
Try calling setStyle() from the onCreate() method
Basicly, if you are trying to use something, what have "dialog" in name, you should build dialog. To do this, there is one method like onCreateDialog().
#NonNull
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(),your_style);
...
return builder.create();
}
Otherwise, you should extend just a Fragment, and there you can call in onCreateView() something like this:
Context contextThemeWrapper = new ContextThemeWrapper(getActivity(),your_style);
LayoutInflater newInflater = inflater.cloneInContext(contextThemeWrapper);
View view = newInflater.inflate(your_layout,container,false);
Sorry for java code, but there should be similar code.
I am trying to access the parent viewpager from inside a fragment, but i have no idea how to do that.
I need to switch the currentItem on the ViewPager after a onClick event inside the fragment.
Any ideas?
EDIT:
I want access to the parent view(ViewPager View) so that i can change the currentItem which is visible, from inside one of my fragments.
From fragment, call getActivity() which will gives you the activity in which the fragment is hosted. Then call findViewById(ViewPagerId) to get the ViewPager.
ViewPager vp=(ViewPager) getActivity().findViewById(ViewPagerId);
Edit: I must add, even though Eldhose answer's works, I would defend my approach. Because the less the fragment knows about the Activity containing it, the better. By doing this, you can get the parent view without depending on IDs, and you can still get information from it even if it isn't a ViewPager.
You can get the in the Fragment's onCreateView method.
The container param is the parent View, in that case, a ViewPager.
In Java:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ViewPager pager = (ViewPager) container;
//.... Rest of your method
}
Kotlin Version:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup,
savedInstanceState: Bundle) {
val pager = container as ViewPager
//.... Rest of your method
}
The methods onCreateView and onViewCreated and onAttach are called too early.
The view is definitely attached to its parent view in the fragment's onResume method. This is a good place to then use getView().getParent()
Another way that helped me :
List<Fragment> fragment = getActivity().getSupportFragmentManager().getFragments();
for (Fragment f:fragment) {
if (f instanceof CreatCheckInFragment){
((ParentFragment) f).viewPager.arrowScroll(View.FOCUS_LEFT);
}
}
I have a fragment added using
transaction.add(R.id.content, fragment, null);
and I need to start new fragment from this one. But to do this I need to know first fragment's container view id (R.id.content in my case). How can I get this?
I can just use this id directly but I suppose fragment shouldn't know such kind of details about parent activity. For example it will be impossible to use this fragment in another activity in this case.
May be "starting" fragment from another one is a bad practice and all fragment handling logic should be handled by activity itself? But creating nice sequences of fragments starting each other seems quite useful (for example detalView->moreDetailView->evenMoreDetailView).
You can access the container's id by calling
((ViewGroup)getView().getParent()).getId();
I don't know if I exactly understand your question, but you get the Container in onCreateView. I suppose you could stash it in a local variable.
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
mContainer = container;
...
}
I think there's a more standard way of accessing the view rather than using
((ViewGroup) getView().getParent()).getId()
I will assume that you're working with a MainActivity that presents a list fragment, which can then present another list fragment upon clicking an item, and so on. I'm assuming that you've chosen to replace the main view of MainActivity with the contents of the list fragments you present.
Because each list fragment is being hosted in the MainActivity, you can always access the view of the MainActivity.
// Inside of onListItemClick...
FragmentManager fm = getFragmentManager();
Fragment fragment = new MyOtherListFragment();
FrameLayout contentView = (FrameLayout) getActivity().findViewById(R.id.content_view);
fm.beginTransaction()
.replace(contentView.getId(), fragment)
.addToBackStack(null)
.commit();
The above example assumes you have an XML layout resource that you set in the MainActivity, call the XML resource R.layout.activity_main, where there is a FrameLayout with the id R.id.content_view. This is the approach I took. The example I present here is a simpler version from the one that I actually wrote in my app.
Incidentally, my version of IntelliJ (version 1.0.1) warns me that
((ViewGroup) getView().getParent)
may throw a NullPointerException.
Assuming you have Fragment instance mCurrentFragment in Activity class.
You can get Fragment's container View via
int id = mCurrentFragment.getView().getParent().getId();
ViewGroup vg = (ViewGroup) findViewById(id); // Fragment's container View
The Kotlin version
val container = view?.parent as? ViewGroup ?: return
It can be added to a "hand-dandy" extension:
fun Fragment.container(): ViewGroup? {
return view?.parent as? ViewGroup
}
Then get the id
container.id
container().id
Add the new class
import androidx.navigation.NavController
class Navigator {
companion object {
var fragment1_id: Int = 0
var fragment2_id: Int = 0
var navController : NavController? = null
fun goFragment1()
{
navController?.navigate(fragment1_id)
}
fun goFragment2()
{
navController?.navigate(fragment2_id)
}
}
}
In main activity:
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
...
val navController = findNavController(R.id.nav_host_fragment_content_main)
Navigator.navController = navController
Navigator.fragment1_id = R.id.nav_fragment1
Navigator.fragment2_id = R.id.nav_fragment2
<navigation xmlns:android...
<fragment
android:id="#+id/nav_fragment1"
...
<fragment
android:id="#+id/nav_fragment2"
Click Listener in any fragment:
fun onClickButton(view: View)
{
Navigator.goFragment1()
}