0

Two activities using the same fragment. The fragment has a text view. Main activity writes “message 1” into the text view and it shows up.

A button in the main activity launches the second activity “for result”.

The Second activity writes “message 2” into the text view and it shows up.

A button in the second activity does set Result Activity.RESULT_OK and then finish().

The main activity gets the “onActivityResult” Result OK and writes “message 3” into the text view. However “Message 3” does not show up in the text view. Instead “message 1” shows up.

public class MainActivity extends AppCompatActivity {
   private static Context  context;
   private static Button   btn_main;

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

       context = this;
       btn_main = findViewById(R.id.btn_main);

       FragmentDisplay.setMessage1("Message 1");

       btn_main.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View v) {
               Intent intent = new Intent(MainActivity.this, SecondActivity.class);
               secondactivityLauncher.launch(intent);
           }
       });
   }
   
   ActivityResultLauncher<Intent> secondactivityLauncher = registerForActivityResult(
           new ActivityResultContracts.StartActivityForResult(),
           new ActivityResultCallback<ActivityResult>() {
               @Override
               public void onActivityResult(ActivityResult result) {
                   if (result.getResultCode() == Activity.RESULT_OK) {
                       FragmentDisplay.setMessage1("Message 3");
                   }

               }
           });

   public static Context getContext(){
       return context;
   }
}
public class SecondActivity extends AppCompatActivity {
    private static Button btn_second;

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

        btn_second = findViewById(R.id.btn_second);

        FragmentDisplay.setMessage1("Message 2");


        btn_second.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                Intent intent = getIntent();
                setResult(Activity.RESULT_OK, intent);
                finish();
            }
        });
    }
}
public class FragmentDisplay extends androidx.fragment.app.Fragment {

    private static TextView textView1;


    public FragmentDisplay() {
        // Required empty public constructor
    }

    RecyclerView mRecyclerView;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.display_fragment, null);

        textView1 = (TextView)view.findViewById(R.id.tv1);
        return view;
    }

    public static void setMessage1(String str){
        textView1.setText(str);
    }

}   // end of class
//activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".main.MainActivity">

<fragment
    android:id="@+id/display_fragment"
    android:name="ddi.pos.display.FragmentDisplay"
    android:layout_width="700dp"
    android:layout_height="180dp"
    android:background="#00CC00"
    android:layout_alignParentLeft="true"
    android:layout_alignParentTop="true"
    android:layout_marginTop="80dp" />

<Button
    android:id="@+id/btn_main"
    android:layout_below="@+id/display_fragment"
    android:layout_marginTop="100dp"
    android:layout_marginLeft="50dp"
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:background="#FFFFFF00"
    android:textSize="25sp"
    android:text="Start Second Activity"
/>
//second_activity.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".main.MainActivity">

<fragment
    android:id="@+id/display_fragment"
    android:name="ddi.pos.display.FragmentDisplay"
    android:layout_width="700dp"
    android:layout_height="180dp"
    android:background="#00CC00"
    android:layout_alignParentLeft="true"
    android:layout_alignParentTop="true"
    android:layout_marginTop="80dp" />

<Button
    android:id="@+id/btn_second"
    android:layout_below="@+id/display_fragment"
    android:layout_marginTop="100dp"
    android:layout_marginLeft="300dp"
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:background="#000000"
    android:textSize="25sp"
    android:text="Finish Second Activity"
/>
//display_fragment.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#CC5500"
>

<TextView android:id="@+id/tv1"
    android:background="#0055FF"
    android:layout_height="60dp"
    android:layout_width="600dp"
    android:text=""
    android:layout_marginLeft="30dp"
    android:layout_marginTop="30dp"
    android:textSize="20dp"
    android:textColor="#ff000000"
/>
Tyler V
  • 9,694
  • 3
  • 26
  • 52
  • The problem is that you have a static TextView (`private static TextView textView1;`). This is a single global value, not unique between fragments. Do not use static views like this - you have no guarantee that the view currently held in the static variable is tied to a displayed Fragment. If you want to pass data between activities and fragments, or among fragments, try having an [activity-level ViewModel](https://stackoverflow.com/questions/60885459/how-to-share-data-between-activity-and-fragment-via-viewmodel-class-in-android) that the fragments can observe. – Tyler V Dec 26 '21 at 20:11
  • To share between activities, use intents or have some data repository/source of truth that is separate from the activities/fragments entirely. – Tyler V Dec 26 '21 at 20:15
  • I am not trying to share data between activities. I want each activity to have its own fragment where it can write stuff in the TextView. It's the same fragment that is added to each activity statically in the xml files. You may be write that the problem is that the TextView is static but I do not know how to settext in it if it is not static. – EliFromToronto Dec 26 '21 at 21:21

1 Answers1

1

Caveat: I suspect that what you posted is not what you actually want to do, but a workaround of some kind so this answer may or may not actually address your use-case. It does, however, produce the behavior you asked for in the question. You said you are not trying to send data between activities, but you want the message in the first activity to change in response to actions in the second activity which implies information may be shared.

Main Answer: The example below, using a shared ViewModel between Activity and Fragment and using data transfer across activities using intents has the behavior you describe in your question.

The ViewModel allows sharing of data between the Activity and Fragment, since the Fragment can observe the LiveData and respond when the activity changes it. Since the question calls startActivityForResult and handles the result, I used those to handle passing data back to change the message.

MainActivity.java

public class MainActivity extends AppCompatActivity {

    ActivityResultLauncher<Intent> secondActivityLauncher = registerForActivityResult(
            new ActivityResultContracts.StartActivityForResult(),
            new ActivityResultCallback<ActivityResult>() {
                @Override
                public void onActivityResult(ActivityResult result) {
                    if (result.getResultCode() == Activity.RESULT_OK) {
                        // as you indicated:
                        //viewModel.setMessage("Message 3");

                        // or like this if you sent data
                        Intent data = result.getData();
                        if( data != null ) {
                            Bundle extras = data.getExtras();
                            if( extras != null ) {
                                String msg = extras.getString("response");
                                viewModel.setMessage(msg);
                            }
                        }
                    }

                }
            });

    private MainViewModel viewModel;

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

        viewModel = new ViewModelProvider(this).get(MainViewModel.class);

        // Always initialize the message to "Message 1"
        viewModel.setMessage("Message 1");

        Button btn = findViewById(R.id.btn_main);
        btn.setOnClickListener(view -> {
            Intent intent = new Intent(MainActivity.this, SecondActivity.class);
            intent.putExtra("message", "Message 2");
            secondActivityLauncher.launch(intent);
        });
    }
}

SecondActivity.java

public class SecondActivity extends AppCompatActivity {

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

        // This ViewModel instance is "not" the same instance as the one from MainActivity, it is
        // just to facilitate communication between the Activity and Fragment
        MainViewModel viewModel = new ViewModelProvider(this).get(MainViewModel.class);

        // as you had it with hard-coded message 2
        // viewModel.setMessage("Message 2");

        //  or like this if you sent the message
        Intent i = getIntent();
        Bundle b = i.getExtras();
        if( b != null ) {
            String msg = b.getString("message");
            viewModel.setMessage(msg);
        }

        Button btn = findViewById(R.id.btn_second);
        btn.setOnClickListener(view -> {
            Intent intent = new Intent();
            intent.putExtra("response", "Message 3");
            setResult(Activity.RESULT_OK, intent);
            finish();
        });
    }
}

MainViewModel.java

public class MainViewModel extends ViewModel {
    private final MutableLiveData<String> message_to_display = new MutableLiveData<>();
    LiveData<String> message() { return message_to_display; }

    void setMessage(String msg) {
        message_to_display.postValue(msg);
    }
}

DisplayFragment.java

public class DisplayFragment extends Fragment {

    public DisplayFragment() {
        // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_display, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        TextView txt = view.findViewById(R.id.tv1);

        // Get the ViewModel from the hosting activity, could be
        // Main or Second, and observe its message. Update the
        // TextView if the message is changed.
        MainViewModel viewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class);
        viewModel.message().observe(getViewLifecycleOwner(), s -> {
            txt.setText(s);
        });
    }
}
Tyler V
  • 9,694
  • 3
  • 26
  • 52
  • 1
    Thank you very much for showing me how to use the ViewModel. The app now works exactly how I intended it to work. – EliFromToronto Dec 27 '21 at 20:02
  • Can ViewModel be used if instead of a TextView in the fragment there is a RecyclerView containg a list of Strings. – EliFromToronto Dec 29 '21 at 12:19
  • Yes, you could have a `LiveData>` and have your activity observe the list of strings and trigger an update of the RecyclerView when the list changes. – Tyler V Dec 29 '21 at 14:38
  • Or have the fragment observe the list, and the activity update it. – Tyler V Dec 29 '21 at 15:03
  • As I am new to this, I have another question. In the MainActivity you instantiate a viewModel. In the DisplayFragment a viewModel is instantiated again. Are there now 2 viewModels instantiated for the MainActivity. An explanation will be greatly appreciated. Thanks. – EliFromToronto Jan 12 '22 at 16:08
  • @EliFromToronto No, they will refer to the same ViewModel instance. The ViewModel factory handles this - you could test it by printing out the view model and seeing for yourself too (`System.out.println("My ViewModel is " + viewModel);`). A ViewModel created with an activity as its scope will live as long as the activity that created it, and getting it from the factory during that time will return that single instance. You can read more about it [here](https://developer.android.com/topic/libraries/architecture/viewmodel#lifecycle) – Tyler V Jan 13 '22 at 04:24