4

I want to have a dialogFragment with a few pages, to enter some settings for my app. When I use AlertDialog.Builder, I get an IllegalStateException saying "Fragment does not have a view", which confuses me (also the stack is pretty useless).

I can get a popup working with the ViewPager in it, doing exactly what I want it to do, but it doesn't have the buttons I want. I really like the look of the default AlertDialog, so I'd preferably use that if possible (for consistency reasons). If not, I'd be okay with making a custom dialog that just looks exactly the same.

So, how can I get the ViewPager working in a AlertDialog / dialogFragment (or similar)?

EDIT: Just to clarify, it doesn't have to be an AlertDialog, but I would like the option to set a title, (permanent) message and buttons.

EDIT2: So it appears the "Fragment does not have a view" error occurs on returning f in FragmentFirst newInstance(). This should return a fragment to the getItem() method in the adapter, which should return the fragment associated with the correct position. I hope this helps anyone pinpointing what's going wrong.

EDIT3: Upon messing around with some stuff, I now got this error: No view found for id 0x7f0c00b7 (com.example.tim.timapp:id/pager) for fragment FragmentDefault{47febfa #1 id=0x7f0c00b7 android:switcher:2131493047:0}, basically telling me something is trying to find the pager view in the wrong fragment? Not quite sure how to fix that. NB. FragmentDefault is exactly the same as FragmentFirst, except for its name and some id's.

EDIT4: (11 Mar '16) A question: When using a newInstance method (like I do inside the fragments), should the returned fragment have a view? When I call if (f.getView() == null), it returns true, which makes me wonder if f even should have a view. If so, why doesn't it? Every newInstance method I see online has exactly the same layout as mine, so it doesn't look like I'm missing something.
Also, for some reason onCreateView in the fragments (FragmentFirst, FragmentSecond, etc.) is never reached. I have a Log.d right after the onCreateView, which never shows in the logcat. Any ideas?

Code:
Calling the dialogFragment

TestActivityFragment newFragment = new TestActivityFragment();  
newFragment.show(getChildFragmentManager(), "dialog");  

TestActivityFragment:

package com.example.fragments.MainFragments.Test;

import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.Fragment;
import android.app.FragmentManager;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v13.app.FragmentStatePagerAdapter;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.example.tim.timapp.R;

public class TestActivityFragment extends DialogFragment {

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        LayoutInflater inflater = getActivity().getLayoutInflater();
        final View view = inflater.inflate(R.layout.test_dialog, null);

        final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setView(view)
                .setPositiveButton("POS", null)
                .setNegativeButton("NEG", null)
                .setTitle("TEST_TITLE")
                .setMessage("TEST_MESSAGE");

        ViewPager mPager = (ViewPager) view.findViewById(R.id.pager);
        FragmentStatePagerAdapter mPagerAdapter = new TestAdapter(getChildFragmentManager());
        mPager.setAdapter(mPagerAdapter);

        return builder.create();
    }

    private class TestAdapter extends FragmentStatePagerAdapter {
        public TestAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            switch(position) {
                case 0: return FragmentFirst.newInstance("FragmentFirst, Instance 1");
                case 1: return FragmentSecond.newInstance("SecondFragment, Instance 1");
                case 2: return FragmentThird.newInstance("ThirdFragment, Instance 1");
                case 3: return FragmentThird.newInstance("ThirdFragment, Instance 2");
                default: return FragmentDefault.newInstance("DefaultFragment");
            }
        }

        @Override
        public int getCount() {
            return 5;
        }
    }
}

One of the fragments

package com.example.fragments.MainFragments.Test;

import android.app.Fragment;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.example.tim.timapp.R;

public class FragmentFirst extends Fragment{

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.frag_first, container, false);

        TextView tv = (TextView) v.findViewById(R.id.tvFragFirst);
        tv.setText(getArguments().getString("msg"));

        return v;
    }

    public static FragmentFirst newInstance(String text) {

        FragmentFirst f = new FragmentFirst();
        Bundle b = new Bundle();
        b.putString("msg", text);

        f.setArguments(b);

        return f;
    }
}

XML's

test_dialog.xml

<?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

(I've had the viewPager as parent layout as well, without anything in it, didn't change a thing)

frag_first.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/tvFragFirst"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        android:text="TextView"
        android:textAppearance="?android:textAppearanceMedium"/>

</RelativeLayout>

Log

03-09 15:30:55.910 23343-23343/? I/art: Not late-enabling -Xcheck:jni (already on)
03-09 15:30:55.940 23343-23349/? E/art: Failed sending reply to debugger: Broken pipe
03-09 15:30:55.940 23343-23349/? I/art: Debugger is no longer active
03-09 15:30:55.950 23343-23343/? W/System: ClassLoader referenced unknown path: /data/app/com.example.tim.timapp-2/lib/x86_64
03-09 15:30:56.060 23343-23356/? D/OpenGLRenderer: Use EGL_SWAP_BEHAVIOR_PRESERVED: true
03-09 15:30:56.150 23343-23356/? I/OpenGLRenderer: Initialized EGL, version 1.4
03-09 15:30:56.210 23343-23356/? W/EGL_emulation: eglSurfaceAttrib not implemented
03-09 15:30:56.210 23343-23356/? W/OpenGLRenderer: Failed to set EGL_SWAP_BEHAVIOR on surface 0x7f5f5597db40, error=EGL_SUCCESS
03-09 15:30:58.870 23343-23343/com.example.tim.timapp D/GSF - getChildCount: 0
03-09 15:30:58.910 23343-23343/com.example.tim.timapp D/AndroidRuntime: Shutting down VM
03-09 15:30:58.910 23343-23343/com.example.tim.timapp E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.tim.timapp, PID: 23343
    java.lang.IllegalStateException: Fragment does not have a view
        at android.app.Fragment$1.onFindViewById(Fragment.java:2181)
        at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:963)
        at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1148)
        at android.app.BackStackRecord.run(BackStackRecord.java:793)
        at android.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1535)
        at android.app.FragmentManagerImpl.executePendingTransactions(FragmentManager.java:562)
        at android.support.v13.app.FragmentStatePagerAdapter.finishUpdate(FragmentStatePagerAdapter.java:168)
        at android.support.v4.view.ViewPager.populate(ViewPager.java:1177)
        at android.support.v4.view.ViewPager.populate(ViewPager.java:1025)
        at android.support.v4.view.ViewPager.onMeasure(ViewPager.java:1545)
        at android.view.View.measure(View.java:18788)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5951)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1465)
        at android.widget.LinearLayout.measureHorizontal(LinearLayout.java:1112)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:632)
        at android.view.View.measure(View.java:18788)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5951)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at android.view.View.measure(View.java:18788)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5951)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at android.view.View.measure(View.java:18788)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5951)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1465)
        at android.widget.LinearLayout.measureVertical(LinearLayout.java:748)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:630)
        at android.view.View.measure(View.java:18788)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5951)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at android.view.View.measure(View.java:18788)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5951)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at android.view.View.measure(View.java:18788)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5951)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at com.android.internal.policy.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2643)
        at android.view.View.measure(View.java:18788)
        at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:2100)
        at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1191)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1452)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1107)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6013)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:858)
        at android.view.Choreographer.doCallbacks(Choreographer.java:670)
        at android.view.Choreographer.doFrame(Choreographer.java:606)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:844)
        at android.os.Handler.handleCallback(Handler.java:739)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:148)
        at android.app.ActivityThread.main(ActivityThread.java:5417)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
03-09 15:35:59.030 23343-23343/? I/Process: Sending signal. PID: 23343 SIG: 9

PS. If I've forgotten to add a piece of code, please let me know, so I can add it.

Community
  • 1
  • 1
Timmiej93
  • 1,328
  • 1
  • 16
  • 35
  • The stacktrace is not useless, it tells you you are missing a view. And that is because you didn't implement onCreateView() in your DialogFragment. See here http://android-developers.blogspot.de/2012/05/using-dialogfragments.html – ElDuderino Mar 10 '16 at 16:46
  • I have already done a few (random) tests with adding the onCreateView() method to the dialogFragment. Since the view gets inflated in the onCreateDialog() method, I thought it'd be expendable. I'll have a more indepth look at your link after dinner. – Timmiej93 Mar 10 '16 at 16:50
  • Just ran a test with onCreateView(). How should I use this exactly? No matter what order, onCreateDialog() gets called before onCreateView, which causes a NPE for the view if you set the view in onCreateView, and other then the view, I don't know what I'd need onCreateView() for. – Timmiej93 Mar 10 '16 at 18:51
  • Yeah I guess I was a bit hasty. Tested it a bit myself, I couldn't get it to work with a viewPager when using onCreateDialog, It works if you use a normal view like a textView instead, but that's no help in your case. When using onCreateView the viewPager works, but you lose all the dialog properties like buttons. Frak fragments. – ElDuderino Mar 11 '16 at 14:29

2 Answers2

4

So, I finally managed to get stuff exactly like I wanted it. Quite some stuff has changed though.


The onCreateDialog() method hasn't changed, except for replacing return builder.create(); with return builder.show();, and some cosmetic changes.


The properly (cough) named TestAdapter is now a PagerAdapter instead of a FragmentStatePagerAdapter. It might not be pretty, but it works.

private class TestAdapter extends PagerAdapter {

    private int mCurrentPosition = -1;

    @Override
    public int getCount() {
        return pageAmount;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        LayoutInflater inflater = getActivity().getLayoutInflater();
        int layoutThingy;

        switch(position) {
            case 0:
                layoutThingy = R.layout.fragment_generalsettingsinputdialog1;
                break;
            case 1:
                layoutThingy = R.layout.fragment_generalsettingsinputdialog2;
                break;
            case 2:
                layoutThingy = R.layout.fragment_generalsettingsinputdialog3;
                break;
            default:
                layoutThingy = R.layout.fragment_viewpagererror;
        }

        View view = inflater.inflate(layoutThingy, null);
        container.addView(view);
        view.requestFocus();
        return view;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        ((ViewPager) container).removeView((View) object);
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == ((View) object);
    }

    @Override
    public void setPrimaryItem(ViewGroup container, int position, Object object) {
        super.setPrimaryItem(container, position, object);
        if (position != mCurrentPosition) {
            View view = (View) object;
            CustomPager pager = (CustomPager) container;
            if (view != null) {
                mCurrentPosition = position;
                pager.measureCurrentView(view);
            }
        }
    }
}

The fragments (those mentioned in the original post as One of the fragments have disappeared. They're not needed anymore.


The test_dialog.xml file has changed as well. It now contains a custom viewpager (and some page indicators, but this is not part of what was troubling me).

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true">

    <com.example.tim.timapp.CustomStuff.CustomPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:overScrollMode="never"/>

    <com.viewpagerindicator.CirclePageIndicator
        android:id="@+id/circlePageIndicator"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:fillColor="@color/colorPrimary"
        app:strokeColor="@color/lightGrey"/>

</LinearLayout>

To continue, the CustomPager looks like this:

package com.example.tim.timapp.CustomStuff;

import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.View;

public class CustomPager extends ViewPager {

    private View mCurrentView;

    public CustomPager(Context context) {
       super(context);
    }

    public CustomPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mCurrentView == null) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            return;
        }

        int height = 0;
        mCurrentView.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
        int h = mCurrentView.getMeasuredHeight();
        if (h > height) height = h;
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    public void measureCurrentView(View currentView) {
        mCurrentView = currentView;
        requestLayout();
    }

    public int measureFragment(View view) {
        if (view == null)
            return 0;

        view.measure(0, 0);
        return view.getMeasuredHeight();
    }
}

It might look daunting, but it doesn't do much more then resizing the viewpager to the size of its current showing page. It doesn't look pretty with all kinds of animations and it's quite slow, but it does the job every time.


And finally, the frag_first.xml XML's. These can be any kind of layout XML you want, as long as it fits in the AlertDialog.


Just to (prove) show that it's working, here's an image.
If you need more info, feel free to ask. I've been struggling with this for a long time, so I'm happy to help.


(Click for larger version)

Timmiej93
  • 1,328
  • 1
  • 16
  • 35
  • Hey, thanks for this epanded answer, I didn't implement custom viewpager, but your code solved a few of my issues! Anyway, Im having trouble getting the keyboard to open when clicking on an edittext. Did you experience that as well? If so, how did you solve it? – MidasLefko Apr 17 '16 at 12:22
  • 1
    I actually did, you can find the solution here: http://stackoverflow.com/a/36264266/5721694 – Timmiej93 Apr 17 '16 at 17:43
  • 1
    You are seriously my hero. I just wish I could upvote twice :) – MidasLefko Apr 17 '16 at 17:58
  • Just here to help. You could always upvote the other answer that helped you ;) – Timmiej93 Apr 17 '16 at 18:01
0

I did something similar but rather then fragment i added custom views in the view pager.

        <android.support.v4.view.ViewPager
            android:id="@+id/view_pager"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <package.CustomViewOne
                android:id="@+id/custom_view_one"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />

            <package.CustomViewTwo
                android:id="@+id/custom_view_two"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />

            <package.CustomViewThree
                android:id="@+id/custom_view_three"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />

        </android.support.v4.view.ViewPager>

My adapter was like :

class SlidePagerAdapter extends PagerAdapter {

    private ViewPager viewPager;

    public SlidePagerAdapter(ViewPager viewPager) {
        this.viewPager = viewPager;
    }

    public View instantiateItem(ViewGroup collection, int position) {
        return viewPager.getChildAt(position);
    }

    @Override
    public int getCount() {
        return viewPager.getChildCount();
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == (object);
    }

    @Override
    public int getItemPosition(Object object) {

        for (int i = 0; i < getCount(); i++) {
            if (object == viewPager.getChildAt(i)) {
                return i;
            }
        }

        return POSITION_NONE;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        viewPager.removeView((View) object);
    }
}

Sample Custom view. (CustomViewOne.Java)

public class CustomViewOne extends LinearLayout {

    public CustomViewOne(Context context) {
        super(context);
        initLayout();
    }

    public CustomViewOne(Context context, AttributeSet attrs) {
        super(context, attrs);
        initLayout();
    }

    public CustomViewOne(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initLayout();
    }

    public void initLayout() {
        LayoutInflater.from(getContext()).inflate(R.layout.custom_view_one_layout, this);
    }
}
Thilek
  • 676
  • 6
  • 18
  • I'm a bit confused on how I would use this. Do I need to create new views? How do the packages work in the xml? – Timmiej93 Mar 09 '16 at 15:43
  • you have to replace the package with the packagename in which view class is. Yes you need to create custom views and inflate your layout inside the custom views. – Thilek Mar 10 '16 at 12:33
  • So in your example the package would be ` – Timmiej93 Mar 10 '16 at 14:39
  • I added a custom view sample in my answer above. So when you create a java class CustomViewOne.java. In the class first line you will see the package name. So take the package name and add the class name next to it. – Thilek Mar 11 '16 at 08:40
  • If I may ask (to better understand what's happening), why do you need three constructors (I believe that's what it's called) for CustomViewOne? The last one seems to do the same stuff the first two do, and adds something. – Timmiej93 Mar 11 '16 at 16:24
  • Well when you create a custom view in Android and extend it with any view group.. this 3 contractors are needed.. even if you remove one, you get compilation error.. each contractors serves its purpose. The first one used when you create the view programatically in the Java code. Their other two are when you insert them in XML layout like we did above and all those attributes you set up there will be taken into account. – Thilek Mar 11 '16 at 21:50