The communication between Fragments
that depend on a Activity
must be done through Activity
and not directly.
For communication between Fragment
and Activity
, although it can be done through casting getActivity()
in Fragment
to the class of Activity
(for example MainActivity
) the recommended form of doing so is through interfaces
, which use Fragments
to make callbacks to Activity
, and in turn, Activity
implements them to respond to Fragments
.
This would cover the basics of communication, down a little more on the subject.
Then we have that the app uses ViewPager
to show the different Fragments
of the app. ViewPager
has two things to keep in mind:
1) Manage the life cycle of the Fragments
. That is to say that based on the% or% of% that we spent when we initialized it, it stays with the class, and then it kills, it creates, and it replaces instances as it suits it. This brings a complexity if we want at some point to be able to invoke something about the Fragments
given that if we keep an instance for later, it may not be the same one that is shown on the screen in a while.
2) The Fragment
prepares the ViewPager
in advance before the Views
is visible, so if we depend on for example Fragment
to show some updated data, that onResume()
can be called when maybe they are not even given the conditions to be able to calculate the update of the screen.
To solve point 1, instead of trying to keep an instance of the onResume()
when creating them for the first time, in the adapter, we make a Fragments
of the method override
, which is called when the instantiateItem
creates a new instance of our ViewPager
, and taking the new instance we replace the previous one in our reference.
The second point is solved using Fragment
which is a Fragment method and is called by the setUserVisibleHint(boolean)
with ViewPager
when it will be displayed on the screen. So we take advantage of this call back to do the last minute updates before Fragment is shown. (Supposedly this method is called correctly, in previous varsiones it could be called with the activity in null and it was necessary to do some extra checks).
Returning to the topic of communication between Activity and Fragment. When it is the true
that sends something to Fragment
of the user, and assuming that it is interactively with the screen, there are no major problems because the Activity
is working and the Activity
as well. When it is the Fragment
that would have to send something to Activity
the problem arises that the Fragment
may not be ready when the Fragment
wants to send a message. So the way I solve it is to make the Activity
request the data to Fragment
when the Activity
is ready to receive it (and in any case you can also verify that the Activity tabmién is ready to send it).
Below is an example of all this:
Layouts:
Fragment One
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tvTitle"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Soy Fragment 1"/>
<Button
android:id="@+id/btnEnviar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/tvTitle"
android:text="Enviar Fecha a Fragment 2"/>
</RelativeLayout>
Fragment Two
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:text="Soy Fragment 2"/>
<TextView
android:id="@+id/tvHoraActual"
android:layout_below="@id/tvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
Main Activity
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
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=".MainActivity">
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="match_parent"
android:layout_height="25dp">
</android.support.design.widget.TabLayout>
<android.support.v4.view.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/tabs"
app:layout_constraintBottom_toBottomOf="parent"
>
</android.support.v4.view.ViewPager>
</android.support.constraint.ConstraintLayout>
Java
Main Activity
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity
implements FragmentUno.FragmentUnoListener, FragmentDos.FragmentDosListener {
private String laFechaActualSegunFragment1;
private ViewPager viewPager;
private MyViewPagerAdapter viewPagerAdapter;
private TabLayout tabs;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewPagerAdapter = new MyViewPagerAdapter(getSupportFragmentManager());
Fragment fragmentUno = new FragmentUno();
viewPagerAdapter.addFragment(fragmentUno,"Frag Uno");
Fragment fragmentDos = new FragmentDos();
viewPagerAdapter.addFragment(fragmentDos,"Frag Dos");
tabs = findViewById(R.id.tabs);
tabs.addTab(tabs.newTab().setText(viewPagerAdapter.getPageTitle(0)),0);
tabs.addTab(tabs.newTab().setText(viewPagerAdapter.getPageTitle(1)),1);
tabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
viewPager.setCurrentItem(tab.getPosition());
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {}
@Override
public void onTabReselected(TabLayout.Tab tab) {}
});
viewPager = findViewById(R.id.viewPager);
viewPager.setAdapter(viewPagerAdapter);
viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabs));
}
@Override
public void onFechaActualIngresada(String horaActual) {
this.laFechaActualSegunFragment1 = horaActual;
viewPager.setCurrentItem(1,true);
}
@Override
public String getHoraActual() {
return this.laFechaActualSegunFragment1;
}
}
ViewPager Adapter
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class MyViewPagerAdapter extends FragmentStatePagerAdapter {
protected final List<Fragment> fragmentList = new ArrayList<>();
protected final List<String> fragmentTitleList = new ArrayList<>();
protected final HashMap<Integer,Fragment> currentInstances = new HashMap<>();
protected FragmentManager fm;
public MyViewPagerAdapter(FragmentManager manager) {
super(manager);
this.fm = manager;
}
@Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
@Override
public int getCount() {
return fragmentList.size();
}
public void addFragment(Fragment fragment, String title) {
fragmentList.add(fragment);
fragmentTitleList.add(title);
}
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
@Override
public CharSequence getPageTitle(int position) {
return fragmentTitleList.get(position);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
currentInstances.put(position, createdFragment);
return createdFragment;
}
public Fragment getCurrentIntance(int position){
return currentInstances.get(position);
}
}
Fragment One (The one that sends a data to Fragment Two)
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import java.util.Date;
public class FragmentUno extends Fragment {
private Button btnEnviar;
private FragmentUnoListener listener;
@Override
public void onAttach(Context context) {
super.onAttach(context);
if(context instanceof FragmentUnoListener){
this.listener = (FragmentUnoListener) context;
}
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View viewRoot = inflater.inflate(R.layout.demo_fragment1_layout,null);
btnEnviar = viewRoot.findViewById(R.id.btnEnviar);
btnEnviar.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String fechaActual = new Date(System.currentTimeMillis()).toString();
listener.onFechaActualIngresada(fechaActual);
}
});
return viewRoot;
}
public static interface FragmentUnoListener{
public void onFechaActualIngresada(String horaActual);
}
}
Fragment Two (The one that receives a data entered from Fragment One)
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class FragmentDos extends Fragment {
private FragmentDosListener listener;
TextView tvHoraActual;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if(context instanceof FragmentDosListener){
this.listener = (FragmentDosListener) context;
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View viewRoot = inflater.inflate(R.layout.demo_fragment2_layout,null);
tvHoraActual = viewRoot.findViewById(R.id.tvHoraActual);
return viewRoot;
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(isVisibleToUser){
String horaActual = listener.getHoraActual();
tvHoraActual.setText(horaActual);
}
}
public static interface FragmentDosListener{
public String getHoraActual();
}
}