Есть ли способ динамически создавать фрагменты и отображать в них данные с использованием одного и того же макета?

Я написал код, который извлекает объект JSON из удаленной конечной точки. По этому вопросу я передаю фиктивный объект, который представляет собой массив объектов. Моя цель - разобрать объект и для каждого элемента массива создать фрагмент в ViewPager. Каждый фрагмент должен отображать идентификатор объекта, который он представляет. Объект выглядит так:

      {'data':[{'id':1},{'id':2}]}

Если я раскомментирую код в FragmentClass, каждый фрагмент отобразит «test». Однако, если я попытаюсь установить текст на основе значения, которое передаю через аргументы, я столкнусь с этой ошибкой:

      java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object android.os.Bundle.get(java.lang.String)' on a null object reference

Код для FragmentClass:

      public class FragmentClass extends Fragment {
    private TextView txtId;

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

        txtId = (TextView) rootView.findViewById(R.id.txtId);
        txtId.setText(getArguments().get("id").toString());

//        txtId.setText("test");
        return rootView;
    }
}

Код для класса ViewPager ScreenSliderPagerActivity:

      public class ScreenSliderPagerActivity extends FragmentActivity {
    private ViewPager mPager;
    private PagerAdapter pagerAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Intent intent = getIntent();
        String data = intent.getStringExtra("data");
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_screen_slide);

        // Instantiate a ViewPager and a PagerAdapter.
        mPager = (ViewPager) findViewById(R.id.pager);
        pagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager(), data);
        mPager.setAdapter(pagerAdapter);
    }

    @Override
    public void onBackPressed() {
        if (mPager.getCurrentItem() == 0) {
            // If the user is currently looking at the first step, allow the system to handle the
            // Back button. This calls finish() on this activity and pops the back stack.
            super.onBackPressed();
        } else {
            // Otherwise, select the previous step.
            mPager.setCurrentItem(mPager.getCurrentItem() - 1);
        }
    }

    private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
        public int NUM_PAGES; //making this a public variable within the inner class allows me to dynamically change the number of fragments created. In this instance, 2 will be created, one for each element of the array.

        public ScreenSlidePagerAdapter(FragmentManager fm, String data) {
            super(fm);
            try {
                JSONObject json = new JSONObject(data);
                JSONArray jsonArray = json.getJSONArray("data");

                NUM_PAGES = jsonArray.length();

                Log.d("NUM_PAGES", String.valueOf(NUM_PAGES)); //2
                FragmentTransaction ft = fm.beginTransaction();
                Bundle args = new Bundle();

// I think the problem is something in the loop, but I cannot seem to figure another way to do this.
                for (int i = 0; i < NUM_PAGES; i++){
                    JSONObject obj = (JSONObject) jsonArray.get(i);

                    FragmentClass fragment = new FragmentClass();
                    ft.add(R.id.pager, fragment);

                    args.putString("id", obj.get("id").toString());
                    fragment.setArguments(args);

                }
                ft.commit();

            } catch (JSONException e) {
                e.printStackTrace();
            }
        }

        @Override
        public Fragment getItem(int position) {
            return new FragmentClass();
        }

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

Наконец, моя MainActivity, содержащая фиктивный объект:

      public class MainActivity extends AppCompatActivity {
    private Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = findViewById(R.id.btn);
    }
    public void handleClick(View view){
        String mockData = "{'data':[{'id':1},{'id':2}]}";
        Intent intent = new Intent(MainActivity.this, ScreenSliderPagerActivity.class);
        intent.putExtra("data",mockData);
        MainActivity.this.startActivity(intent);

    }
}

2 ответа

Решение

FragmentStatePagerAdapterтак не работает. Вы не можете добавить свои фрагменты в конструкторе вашего адаптера в диспетчер фрагментов и ожидать, что он будет добавлен в нужном месте. Вы должны вернуть построенный фрагмент с аргументами в getItem(int) метод:

      private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
    public int NUM_PAGES; //making this a public variable within the inner class allows me to dynamically change the number of fragments created. In this instance, 2 will be created, one for each element of the array.
    private ArrayList<FragmentClass> mFragments = new ArrayList<>();

    public ScreenSlidePagerAdapter(FragmentManager fm, String data) {
        super(fm);
        try {
            JSONObject json = new JSONObject(data);
            JSONArray jsonArray = json.getJSONArray("data");

            NUM_PAGES = jsonArray.length();

            Log.d("NUM_PAGES", String.valueOf(NUM_PAGES)); //2
            

            for (int i = 0; i < NUM_PAGES; i++){
                JSONObject obj = (JSONObject) jsonArray.get(i);
                Bundle args = new Bundle();
                FragmentClass fragment = new FragmentClass();
                args.putString("id", obj.get("id").toString());
                fragment.setArguments(args);
                mFragments.add(fragment);
            }

        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Fragment getItem(int position) {
        return mFragments.get(position);
    }

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

Чтобы решить эту проблему, нам действительно нужно понимать, как работает адаптер.

Функция getItem() в адаптере будет отображать фрагмент в соответствии с положением элемента. Видите ли, вы не передали аргумент внутри этого фрагмента, который возвращается здесь.

              @Override
        public Fragment getItem(int position) {
            return new FragmentClass();  // <- here you just created a branch new Fragment
        }

Вы не должны выполнять транзакцию фрагмента самостоятельно внутри конструктора адаптера. Вот что делает адаптер.

Обычный способ сделать это - инициализировать список параметров и просто передать его во фрагмент, который возвращается в getItem().

         
   private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
        public int NUM_PAGES;
        public JSONArray jsonArray;

        public ScreenSlidePagerAdapter(FragmentManager fm, String data) {
            super(fm);

            JSONObject json = new JSONObject(data);
            JSONArray jsonArray = json.getJSONArray("data");

        }

        @Override
        public Fragment getItem(int position) {
            
            JSONObject obj = jsonArray.get(i);
            
            Fragment f = FragmentClass();
            Bundle args = new Bundle();
            args.putString("id", obj.get("id").toString());
            f.setArguments(args);
            return f;
        }

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