After configuration change, the observer is not triggered anymore in my fragment. I really can't understand what I'm missing in the code (it is using latest androidx support libraries).
Activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
FragmentManager fragMgr = getSupportFragmentManager();
itemListFragment = (ItemListFragment) fragMgr.findFragmentById(R.id.container_fragment_items);
if (itemListFragment == null) {
itemListFragment = ItemListFragment.newInstance();
fragMgr.beginTransaction()
.add(R.id.container_fragment_items, itemListFragment)
.commit();
}
...
Fragment:
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_item_list, container, false);
mainViewModel = ViewModelProviders.of(this).get(MainViewModel.class);
mainViewModel.getAllItems().observe(getViewLifecycleOwner(), items -> adapter.setItems(items));
...
MainViewModel:
public class MainViewModel extends AndroidViewModel {
private MainRepository mMainRepository;
private LiveData<List<Item>> mAllItems;
public MainViewModel(Application application) {
super(application);
mMainRepository = new MainRepository(application);
mAllItems = mMainRepository.getAll();
}
public LiveData<List<Item>> getAllItems() {
return mAllItems;
}
...
Related
I'm a beginner who learning Android basic course
I want to make 6 fragments that have same structure (only title, image, texts will be different) by using single fragment.
I tried this code but I can see only one fragment.
MainActivity.java
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewPager = (ViewPager) findViewById(R.id.fragment_container);
MoviePagerAdapter adapter = new MoviePagerAdapter(getSupportFragmentManager());
mainfragment = new MainFragment();
adapter.addItem(mainfragment);
viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(6);
}
ViewPager Adapter (in MainActivity)
class MoviePagerAdapter extends FragmentStatePagerAdapter {
public ArrayList<Fragment> items = new ArrayList<Fragment>();
#Override
public Fragment getItem(int position) {
return items.get(position);
}
public MoviePagerAdapter(#NonNull FragmentManager fm) {
super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
}
public void addItem(Fragment item) {
items.add(item);
}
#Override
public int getCount() {
return items.size();
}
}
MainFragment.java
public class MainFragment extends androidx.fragment.app.Fragment {
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.main_frargment, container,
false);
//...
'i want to make 6fragments with datas below '
movieData = new MovieData("title1", "rate1", "grade1");
addData(movieData);
movieData = new MovieData("title2", "rate2", "grade2");
addData(movieData);
movieData = new MovieData("title3", "rate3", "grade3");
addData(movieData);
movieData = new MovieData("title4", "rate4", "grade4");
addData(movieData);
movieData = new MovieData("title5", "rate5", "grade5");
addData(movieData);
movieData = new MovieData("title6", "rate6", "grade6");
addData(movieData);
return rootView;
}
public void addData(MovieData movieData){
Bundle bundle = new Bundle();
bundle.putParcelable("data", movieData);
MainFragment mainFragment = new MainFragment();
mainFragment.setArguments(bundle);
title.setText(movieData.title);
reservation_rate.setText(movieData.getReservation_rate());
grade.setText(movieData.getGrade());
image.setImageResource(R.drawable.image6);
}
}
change your code like this:
public class MainActivity extends AppCompatActivity {
private MoviePagerAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewPager = (ViewPager) findViewById(R.id.fragment_container);
adapter = new MoviePagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(6);
for(int i =0;i<6;i++){
createFrag(i);
}
}
}
private void createFrag(int number) {
MainFragment fragment = new MainFragment();
Bundle args = new Bundle();
args.putInt("NUMBER",number);
fragment.setArguments(args);
adapter.addItem(fragment);
adapter.notifyDataSetChanged();
}
class MoviePagerAdapter extends FragmentStatePagerAdapter {
public ArrayList<Fragment> items = new ArrayList<Fragment>();
#Override
public Fragment getItem(int position) {
return items.get(position);
}
public MoviePagerAdapter(#NonNull FragmentManager fm) {
super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
}
public void addItem(Fragment item) {
items.add(item);
}
#Override
public int getCount() {
return items.size();
}
}
and change your Fragment like this:
public class MainFragment extends androidx.fragment.app.Fragment {
//1st
private int number;
#Nullable
//this is variant 1 - with static Movie data
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable
ViewGroup container, #Nullable Bundle savedInstanceState) {
ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.main_frargment,
container,
false);
//...
//2d
assert getArguments() != null;
number = getArguments().getInt("NUMBER");
switch (number) {
case 0:
MmovieData movieData1 = new MovieData("title1", "rate1",
"grade1");
addData(movieData1);
break;
case 1:
MmovieData movieData2 = new MovieData("title2", "rate2",
"grade2");
addData(movieData2);
break;
//...
}
return rootView;
}
public void addData(MovieData movieData){
title.setText(movieData.title);
reservation_rate.setText(movieData.getReservation_rate());
grade.setText(movieData.getGrade());
image.setImageResource(R.drawable.image6);
}
}
and this is variant 2 ( with dynamic data and listener)
public class MainFragment extends androidx.fragment.app.Fragment {
//1st
private int number;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable
ViewGroup container, #Nullable Bundle savedInstanceState) {
ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.main_frargment,
container,
false);
//...
//2d
assert getArguments() != null;
number = getArguments().getInt("NUMBER");
return rootView;
}
//some method of listener changed
public void dataChanged(MovieData movieData, int position){
if(number==position){
title.setText(movieData.title);
reservation_rate.setText(movieData.getReservation_rate());
grade.setText(movieData.getGrade());
image.setImageResource(R.drawable.image6);
}
}
After changing to the Android Navigation Component I encountered some problems with migrating my Activity Transitions to the corresponding Fragment Transitions.
Specifically, it appears that the EnterTransition of a Fragment doesn't apply to it's Child Fragments.
I've set up a test activity that contains an OuterTestFragment, which in turn consists of a TextView and another fragment, InnerTestFragment.
A button in the activity then replaces the OuterTestFragment with a new OuterTestFragment to see if the contents transition smoothly.
Here are the relevant classes:
Activity:
public class PlaygroundActivity extends AppCompatActivity {
public static final ArrayList<String> strings = new ArrayList<>();
static {
strings.add("sgdgdgsdfggggggggsdgsdfgsd\nsdsdgsdfgds\ndfgsd\nsdsdgsdfgds\ndfgsd");
strings.add("sgdgdgsdfgggg\nggggsndfgsd\nsdsdgsdfgds\ndfgsd");
strings.add("sgdgsd\nsdsdgsdfgds\ndfgsd");
strings.add("sgdgdgsdfggfgds\ndfgsd");
strings.add("sgdgdgsdfggggggggsdg");
}
int count = 1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_playground);
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragmentContainerOuter, OuterTestFragment.getInstance("This is an inner text", "Outer text!"))
.commit();
}
public void replace(View v) {
getSupportFragmentManager().beginTransaction()
.setReorderingAllowed(true)
.addToBackStack("replaced")
.replace(R.id.fragmentContainerOuter, InnerTestFragment.getInstance(count++ >= 4 ? "Default" : strings.get(count)))
.commit();
}
}
OuterTestFragment:
public class OuterTestFragment extends TransitionedFragment {
private static final String ARG_OUTER_TEXT = "OuterTestFragment:outerText";
private static final String ARG_INNER_TEXT = "OuterTestFragment:innerText";
private String innerText;
private String outerText;
public static OuterTestFragment getInstance(String inner, String outer) {
OuterTestFragment outerFragment = new OuterTestFragment();
Bundle args = new Bundle();
args.putString(ARG_OUTER_TEXT, outer);
args.putString(ARG_INNER_TEXT, inner);
outerFragment.setArguments(args);
return outerFragment;
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
innerText = getArguments().getString(ARG_INNER_TEXT);
outerText = getArguments().getString(ARG_OUTER_TEXT);
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.test_fragment_outer, container, false);
((TextView) layout.findViewById(R.id.textOuter)).setText(outerText);
getFragmentManager().beginTransaction()
.replace(R.id.fragmentContainer, InnerTestFragment.getInstance(innerText))
.commit();
return layout;
}
}
TransitionedFragment:
public abstract class TransitionedFragment extends Fragment {
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TransitionInflater inflater = TransitionInflater.from(getContext());
setEnterTransition(inflater.inflateTransition(R.transition.default_transition));
setReenterTransition(inflater.inflateTransition(R.transition.default_transition));
setSharedElementEnterTransition(inflater.inflateTransition(R.transition.scaled_img_clip_transition));
}
}
Inner Test Fragment:
public class InnerTestFragment extends Fragment {
private static final String ARG_TEXT = "InnerTestFragment:text";
private String text;
public static InnerTestFragment getInstance(String text) {
InnerTestFragment frgmt = new InnerTestFragment();
Bundle args = new Bundle();
args.putString(ARG_TEXT, text);
frgmt.setArguments(args);
return frgmt;
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
text = getArguments().getString(ARG_TEXT);
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
LinearLayout layout = (LinearLayout) inflater.inflate(R.layout.test_fragment_inner, container, false);
((TextView) layout.findViewById(R.id.text)).setText(text);
return layout;
}
}
Any help is much appreciated, as the Transition API drives me nuts....
You may show transitions with the setEnterTransition method.
Fragment fragment = InnerTestFragment.getInstance(innerText);
Fade enterFade = new Fade(); //can be any transition, SlideTransition or Inflated Transitions
enterFade.setDuration(300); //Duration of transition in ms
fragment.setEnterTransition(enterFade);
getFragmentManager().beginTransaction()
.replace(R.id.fragmentContainer, fragment)
.addToBackStack(fragment.getClass().getSimpleName())
.commit();
Try to add a background color to the child fragment...
I'm writing an Android application with ViewPager. My ViewPager contains two Fragments. In each fragment are located two receclerViews to show different LiveData lists of items from Database (I'm using Room)
The code for BaseFragment
public class BaseFragment extends Fragment {
private RecyclerView recyclerView;
private NewsAdapter adapter;
public ViewModel mViewModel;
public BaseFragment() {
// Required empty public constructor
}
#Override
public void onCreate(#Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mViewModel = ViewModelProviders.of(this).get(ViewModel.class);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View mView = inflater.inflate(R.layout.fragment_feeds, container, false);
recyclerView = mView.findViewById(R.id.feeds_recycler_view);
return mView;
}
#Override
public void onActivityCreated(#Nullable final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setUpRecyclerView();
mViewModel.getAllNewsEntities()
.observe(this, allNewsEntities -> {
NewsEntityUtilCallback productDiffUtilCallback =
new NewsEntityUtilCallback(adapter.getNewsItems(), allNewsEntities);
DiffUtil.DiffResult newsEntitiesDiffResult = DiffUtil.calculateDiff(productDiffUtilCallback);
adapter.setNewsItems(allNewsEntities);
newsEntitiesDiffResult.dispatchUpdatesTo(adapter);
});
}
protected void setUpRecyclerView() {
LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
recyclerView.setLayoutManager(layoutManager);
ItemClickListener onClickListener = (v, position) -> {
String url = adapter.getNewsItems().get(position).getLink();
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
if (browserIntent.resolveActivity(getContext().getPackageManager()) != null) {
startActivity(browserIntent);
}
};
adapter = new NewsAdapter(getContext(), onClickListener);
recyclerView.setAdapter(adapter);
}
}
Look closely at this code section:
mViewModel.getAllNewsEntities()
.observe(this, allNewsEntities -> {
NewsEntityUtilCallback productDiffUtilCallback =
new NewsEntityUtilCallback(adapter.getNewsItems(), allNewsEntities);
DiffUtil.DiffResult newsEntitiesDiffResult = DiffUtil.calculateDiff(productDiffUtilCallback);
adapter.setNewsItems(allNewsEntities);
newsEntitiesDiffResult.dispatchUpdatesTo(adapter);
});
}
Here I'm going to observe different LiveData queries in each Fragment(in the second Fragment it's mViewModel.getAllBookmarkedNewsEntities()).The other things my code will be equals (the same lifecycles methods, the same RecyclerView). So could give me advice about the best possible design principle to refactor my code. I don't want simply to copy my code in another Fragment class just because of one line
Just extends the BaseFragment and override onActivityCreated method. Something like this:
public class ReuseBaseFragment extends BaseFragment {
#Override
public void onActivityCreated(#Nullable final Bundle savedInstanceState) {
//Your new code here
}
}
In my MainActivity there is a FrameLayout (id=fragment_container) that is replaced by the Activity in onCreate with FragmentA. FragmentA just contains a single Button and calls onClick in the MainActivity in order to replace the FrameLayout with FragmentB. FragmentB contains a RecyclerView and is shown correctly. I call fragmentManager.popBackStack() to bring back FragmentA. Now FragmentA is shown on top of FragmentB (which I still can interact with).
Why is FragmentB not removed after calling popBackStack?
MainActivity
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private FragmentManager fragmentManager;
#Override
public void onBackPressed() {
fragmentManager.popBackStack();
}
#Override
public void onClick(View view) {
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, new FragmentB());
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//...
fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
FragmentA fragment = FragmentA.getInstance(this); //register OnClickListener
fragmentTransaction.replace(R.id.fragment_container, fragment);
fragmentTransaction.commit();
}
//...
}
FragmentA
public class FragmentA extends Fragment {
private View.OnClickListener onClickListener;
public FragmentA() {
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_a, container, false);
FloatingActionButton fab = (FloatingActionButton) view.findViewById(R.id.fab);
fab.setOnClickListener(onClickListener);
return view;
}
public static FragmentA getInstance(View.OnClickListener onClickListener) {
FragmentA fragmentA = new FragmentA();
fragmentA.setOnClickListener(onClickListener);
return fragmentA;
}
private void setOnClickListener(View.OnClickListener onClickListener) {
this.onClickListener = onClickListener;
}
}
FragmentB
public class FragmentB extends Fragment {
//...
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.FragmentB, container, true);
//manipulating Views...
//...
return view;
}
}
The problem is in the FragmentB class. You should not pass the argument attachToRoot=true.
Try this instead:
public class FragmentB extends Fragment {
//...
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
//don't pass true
View view = inflater.inflate(R.layout.FragmentB, container, false);
//manipulating Views...
//...
return view;
}
}
Use popBackStackImmediate instead,
#Override
public void onBackPressed() {
fragmentManager.popBackStackImmediate();
}
From the popBackStackImmediate docs,
Like popBackStack(), but performs the operation immediately inside of the call. This is like calling executePendingTransactions() afterwards.
I want to change the Fragment when i click the button. The fragments are in the same adapter/viewpager.
FROM THIS FRAGMENT:
public class LoginFragment extends Fragment {
TextView linkToRegister;
public static final LoginFragment newInstance()
{
LoginFragment mf = new LoginFragment();
Bundle bd = new Bundle(1);
mf.setArguments(bd);
return mf;
}
public View onCreateView(LayoutInflater inflater, final ViewGroup container, Bundle savedInstanceState )
{
View v = inflater.inflate(R.layout.fragment_login, container, false);
linkToRegister = (TextView) v.findViewById(R.id.link_to_register);
linkToRegister.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Change Fragment
}
});
return v;
}
}
TO THIS FRAGMENT:
public class RegisterFragment extends Fragment {
public static final String EXTRA_MESSAGE = "EXTRA_MESSAGE";
public static final RegisterFragment newInstance()
{
RegisterFragment mf = new RegisterFragment();
Bundle bd = new Bundle(1);
mf.setArguments(bd);
return mf;
}
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState )
{
View v = inflater.inflate(R.layout.fragment_register, container, false);
return v;
}
}
PagerAdapter:
public class WelcomePagerAdapter extends FragmentStatePagerAdapter {
private final List<Fragment> Welcomefragments;
public WelcomePagerAdapter(FragmentManager fm, List<Fragment> Welcomefragments) {
super(fm);
this.Welcomefragments = Welcomefragments;
}
#Override
public Fragment getItem(int position) {
return this.Welcomefragments.get(position);
}
#Override
public int getCount() {
return this.Welcomefragments.size();
}
}
Activity:
public class WelcomeActivity extends AppCompatActivity {
ViewPager welcomeViewPager;
WelcomePagerAdapter welcomePagerAdapter;
List<Fragment> welcomeFragments;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_welcome);
welcomeViewPager = (ViewPager) findViewById(R.id.viewPagerWelcome);
welcomeFragments = getWelcomeFragments();
welcomePagerAdapter = new WelcomePagerAdapter(getSupportFragmentManager(), welcomeFragments);
welcomeViewPager.setAdapter(welcomePagerAdapter);
}
public List<Fragment> getWelcomeFragments() {
List<Fragment> newFragment = new ArrayList<Fragment>();
newFragment.add(LoginFragment.newInstance());
newFragment.add(RegisterFragment.newInstance());
return newFragment;
}
}
Step 1: Define a callback interface in the Fragment and hook in the Activity when the Fragment attaches to it.
public class MyFragment extends Fragment {
public interface OnInteractionListener {
void doAction(); // Can include parameters here if needed
}
private OnInteractionListener listener;
#Override
public void onAttach(Context context) {
if (context instanceof OnInteractionListener) {
listener = (OnInteractionListener) context;
} else {
throw new ClassCastException(context + "must implement " + OnInteractionListener.class.getSimpleName());
}
super.onAttach(context);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_layout, container, false);
// ...
return v;
}
}
Step 2: Make sure your Activity implements that Interface
public class MyActivity extends AppCompatActivity implements MyFragment.OnInteractionListener {
#Override
public void doAction() {
// TODO: Implement this... e.g. switch fragments
}
...
}
Step 3: Inside the Fragment, you can invoke the callback whenever you want to perform some action
if (listener != null) {
listener.doAction();
}
From MainActivity(), get the navController and then use the navigate() function:
NavController navController = Navigation.findNavController(this,R.id.nav_host_fragment);
navController.navigate(R.id.nav_grid);