0

Building on a previous question and using fragment based approach, the question now is How to launch a fragment from a fragment while passing a custom data object

public class MainActivity extends AppCompatActivity {

    public User user = new User();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        getSupportFragmentManager().beginTransaction()
                .replace(R.id.frame_layout, user)
                .commit();
    }
}

User.java list of users

public class User extends Fragment {

    ListView userList;
    private ArrayList<UserVO> users;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        View view =  inflater.inflate(R.layout.user_list, container, false); // Inflate the layout for this fragment
        userList = view.findViewById(R.id.userList);

        users = getUsers(); // ArrayList

        userList.setAdapter(new UserAdapter());
        userList.setOnItemClickListener((adapterView, view1, i, l) -> {
            Log.d("UserList", "onItemClick: " + i);

            // how to launch Form.java Fragment while passing users[i]
        });

        return view;
    }

    class UserAdapter extends BaseAdapter {

        @Override
        public int getCount() {
            return users.size();
        }

        @Override
        public Object getItem(int i) {
            return null;
        }

        @Override
        public long getItemId(int i) {
            return 0;
        }

        @Override
        public View getView(int i, View view, ViewGroup viewGroup) {
            view = LayoutInflater.from(viewGroup.getContext()).inflate(android.R.layout.simple_list_item_1, viewGroup,false);
            ((TextView) view.findViewById(android.R.id.text1)).setText(users.get(i).givenName());
            return view;
        }
    }
}

Form.java which is basically to display user data in a form for update purposes.

public class Form extends Fragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view =  inflater.inflate(R.layout.form, container, false);
        return view;
    }
}

Q1. Within User.java I've a placeholder in the comments, asking for how to instantiate Form.java while passing users[i] (a custom object with fields)

Q2. Please see if there's anything wrong with the approach in MainActivity.java, I've seen the other approach with add method and I'm using replace here, not sure which one is right here.


Attempt

I've added following code and not happy from the result, it's overlapping with the list, I want form to have it's own view and place on the stack. Back button should take the user back to the list.

Form form = new Form();

getFragmentManager().beginTransaction()
    .add(R.id.frame_layout, form)
    .commit();

Edit 2 For review - based on the answer.

public interface IForm { // 1
   void update(UserObject userObject);
}

public class User extends Fragments implements IForm { // 2
    private Form form = new Form();

    void update(UserObject userObject) {
         // update user list with updated object.
    }

public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

    View view =  inflater.inflate(R.layout.user_list, container, false); // Inflate the layout for this fragment
    userList = view.findViewById(R.id.userList);

    users = getUsers(); // ArrayList

    form.delegate = this; // 3

    userList.setOnItemClickListener((adapterView, view1, i, l) -> {
        Log.d("UserList_archive", "onItemClick: " + i);
        form.userData = users[i]; // 4
        getFragmentManager().beginTransaction()
                .replace(R.id.frame_layout, form)
                .addToBackStack(null)
                .commit();
    });

    return view;
}

public class Form {
    public IForm delegate; // 5
    public UserObject userObject; // see 4 - to display data in the form
}
Developer
  • 924
  • 3
  • 14
  • 30
  • ok, you can do it with an interface as well :) – Ranjit Apr 11 '19 at 11:38
  • You need to go through the activity, [easiest way is using an interface](https://developer.android.com/training/basics/fragments/communicating). – Leonardo Velozo Apr 11 '19 at 11:39
  • yes communication is 1, but I'm having another issue, see my edit "Attempt" and views are overlapping, certainly this is not what I want. It's a two part problem, display a form and pass the custom object. – Developer Apr 11 '19 at 11:40
  • probably it's overlapping because you did not set a background on the fragment's root layout – Tim Apr 11 '19 at 11:42
  • Assuming I'm following your setup correctly, just `replace()` instead. You seem to have a severe aversion to `Fragment`s, for some odd reason. – Mike M. Apr 11 '19 at 11:53
  • "Back button should take the user back to the list." – Insert `addToBackStack(null)` after `replace()`. – Mike M. Apr 11 '19 at 11:56
  • Thanks @MikeM. `addToBackStack(null)` worked – Developer Apr 11 '19 at 11:59
  • I would totally consider using an event-driven design here: when you want to launch it, notify the activity from the current fragment and let it do it for you. The job of the activity (when using Fragments) is to manage them for you. – Eenvincible Apr 11 '19 at 12:18
  • @Eenvincible fascinating, i love when I've to write less code and let the platform do it's thing, the right way but I'm a newbie and I'm very curious on how I can implement this,I understand your view but I don't know how to do it, would highly appreciate if you can provide a code example of going from `User` (list) to `Form` and back while passing custom object. – Developer Apr 11 '19 at 12:26
  • @Eenvincible please review **Edit 2** for `getFragmentManager()`, is this what you were referring to? – Developer Apr 11 '19 at 12:47

1 Answers1

0

Here you have to use one CallBack/Interface to plug and play with two fragments.

First, write one interface with a method which will do the job, like below:

public interface ItemClickListener extends Parcelable{

   void onItemClick(FormData data);
}

Then initialize this interface in your both Fragments in OnAttach() :

public class UserFragment extends Fragment {

private ItemClickListener onItemClick;

 @Override
public void onAttach(Context context) {
    super.onAttach(context);
    onItemClick= (ItemClickListener) context;
}

Use it inside your onItemClick to pass the value:

 userList.setOnItemClickListener((adapterView, view1, i, l) -> {
        Log.d("UserList", "onItemClick: " + i);

        // how to launch Form.java Fragment while passing users[i]
        onItemClick.onItemClick(adapterView.getItemAtPosition(i);
    });
}

Then implement this interface in your Activity.

public class MainActivity extends AppCompatActivity implements ItemClickListener{

   @override
    public void onItemClick(FormData data){
       //do your stuff here with form data or whatever your object
       //Start Form Fragment and send the data through Bundle
       //https://stackoverflow.com/questions/12739909/send-data-from-activity-to-fragment-in-android 
    }
}
Ranjit
  • 5,130
  • 3
  • 30
  • 66
  • I tested this approach, however the `Context` was MainActivity and I wanted to have `List` because List is the parent of the Form, `MainActivity` is the root child (remember I don't want MainActivity to do anything). Secondly please review my Edit 2 and see the tags 1,2,3,4 for the main pieces I want you to look at. Basically the knowledge is one way so it's losely coupled, `User` knows `Form` but `Form` doesn't know `User` and it only know something through an interface `IForm` – Developer Apr 11 '19 at 12:22
  • @Developer Why you implement IForm in Fragment. It should be implemented by the MainActivity. – Ranjit Apr 11 '19 at 12:27
  • the architecture is whatever the root object (MainActivity in this case) has to be codeless, nothing should be there and just merely instantiation of a fragment, logic has to be handled in a custom class of each view. Sort of like ViewController (not mvc but mvp etc.) – Developer Apr 11 '19 at 12:29
  • @Developer MainActivity will not do anything here. It will only replace fragments when needed. And the callback will let know the MainActivity to do when needed. i.e after onItemClick of the First Fragment. – Ranjit Apr 11 '19 at 12:29
  • Because Activity can able to start or repleace the Fragment. And this is the correct approach. – Ranjit Apr 11 '19 at 12:32
  • I'll accept your answer but for the sake of argument, in this architecture, grandchildren (`Form`) should not know about grandparent (`MainActivity`), so a child (`Form`) should only know about it's parent (`UserList`) only. also are you ok with this line `form.userData = users[i]; // 4` – Developer Apr 11 '19 at 12:54
  • @Developer I never told you to accept my answer if you are still not able to understand. In this approach, Form will never know the MainActivity. Here Form will know that the data is coming from the UserList. MainActivity is only the Context. To be honest, passing data between Fragments are done by the mediating of the Parent Activity. And the ParentActivity will only do the work to replace the fragment inside the container when needed. – Ranjit Apr 11 '19 at 13:01
  • 1
    form.userData = users[i]; // 4 is not an issue. But its better to use getItemByPosition which will give the proper clicked data. – Ranjit Apr 11 '19 at 13:03
  • When you say ParentActivity you mean MainActivity, and when you say replace that means the MainActivity becomes the direct parent of both Fragments (User and Form)? – Developer Apr 11 '19 at 13:04
  • 1
    Yes. Here both Fragments are belongs to one Activity only. So MainActivity is the parent of both. There is no Grand Parent :) – Ranjit Apr 11 '19 at 13:06
  • 1
    Always rember, Fragment is from Activity, not Fragment. – Ranjit Apr 11 '19 at 13:07
  • ok, that means my approach under **Edit2** is wrong? I'm launching a fragment from a fragment using `getFragmentManager().beginTransaction()`, and can you update your answer under `setOnItemClickListener ` with the right approach in order for the form to show up – Developer Apr 11 '19 at 13:12
  • hey buddy, still waiting if you can finalize this with this last step, we are close, highly appreciate. – Developer Apr 11 '19 at 14:20
  • @Developer I already written the approach. First create a interface with one itemClick method which can have a parameter to pass value. Then implement that interface only in MainActivity (ParentActivity of all Fragments) and override the itemClick method. Then Define the interface in User Fragment and intitialize in its onAttach(Context c) method (see my example in my answer). Then in list item on click, just call the itemClick method by the instance of the interface. It will direct navigate to the MainActivity's itemClick method (update method in your case) . – Ranjit Apr 11 '19 at 17:09
  • Then just replace the Form Fragment inside itemClick/update method from MainActivity. Use Bundle to or any Global class to pass data to the Form Fragment. I already shown all these steps with example. – Ranjit Apr 11 '19 at 17:09