I have Fragments with a play button in my fragment. Whenever the user presses the play button from the ViewPager fragment, it calls a function in host activity which plays the player.
I am trying to implement the live data by using a ViewModel but unsuccessful.
public class SlidePlayerFragment extends Fragment {
private MyViewModel viewModel;
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
final View view = inflater.inflate(R.layout.fragment_slide_player, container, false);
ImageView playBtn = view.findViewById(R.id.playBtn);
playBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
viewModel.getIsPlaying().setValue(true);
viewModel.getCheck().setValue("Checking");
}
});
return view;
}
}
}
Here's my PagerAdapter
public class MyViewPagerAdapter extends FragmentStatePagerAdapter {
private int pages;
public MyViewPagerAdapter(FragmentManager fm,int count) {
super(fm);
pages = count;
}
#Override
public Fragment getItem(int i) {
return new SlidePlayerFragment();
}
#Override
public int getCount() {
return pages;
}
}
Here is the view model function
private MutableLiveData<Boolean> isPlaying;
public MutableLiveData<Boolean> getIsPlaying() {
if (isPlaying == null)
isPlaying = new MutableLiveData<>();
Log.d("TAG","Checking "+isPlaying.getValue());
return isPlaying;
}
And here is the observer segment
final Observer<Boolean> isPlaying= isPlaying -> {
//code here if playing then do some task here
};
viewModel.getIsPlaying().observe(this,isPlaying);
Note I already tried to find the predefined solution but I didn't find anything.
You do not really have to have a ViewModel for this purpose. You might just consider having public functions in the Activity which hosts the Fragments. For example, let us consider you have the following public functions in your PlayerActivity which launches all the other fragments.
public void play(String songName) {
viewModel.getIsPlaying().setValue(true);
viewModel.getCheck().setValue("Checking");
// Other actions for playing
}
Now, you might just consider the following onClick function instead of the one you are having now.
playBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
((PlayerActivity) getActivity()).play(songName);
}
});
Hope that helps!
Related
In my app, I have an activity that uses fragment(s). In one such fragment, I am using a recyclerview.
I just want to add (one or more) onClick(s) whose action I can process in the fragment or in the activity as appropriate.
I searched a lot. I found many solutions, but none that seems to me to really respond to my case, which is however not so exceptional.
I found a lot of solutions with the onClick in OnBindviewHolder. However, I also read very often that this solution was not acceptable because it consumed unnecessary resources.
I found a solution (https://openclassrooms.com/en/courses/4568576-recover-and-display-distant-data/4893791-interact-with-the-recyclerview) using a itemClickSupport class that works, but I don't know how to fully exploit it.
So, I'm going around in circles, and I don't know which track I should go to, without wasting my time unnecessarily realizing at the end that I didn't go in the right direction.
Edit :
Another solution that seems perfect to me (https://openclassrooms.com/fr/courses/4568596-construisez-une-interface-utilisateur-flexible-et-adaptative/4789616-creez-votre-premiere-application-avec-des-fragments), automatically creates it in the fragment by a callback, BUT, I don't know how to get it in the parent activity.
What I really want is :
on smartphone :
an activity that open a fragment 1 with a recyclerview
another activity with another fragment (with another recyclerview) opened by a click on a button in the recyclerview from the fragment 1
on tablet (with another layout) :
an activity that open both fragments, and the button in the fragment/recyclerview 1 populate the fragment/recyclerview 2.
I realize that I have a lot of trouble explaining what I want, and that my problem to find the solution probably comes from there.
I think I finally found a solution that can be useful :
activity:
public class MainActivity extends FragmentActivity implements MainFragment.SampleOnClickedListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
public void sampleOnClicked(int position) {
Log.e("TAG", " from activity / position:" + position);
}
}
fragment:
public class MainFragment extends Fragment implements SampleAdapter.SampleRecyclerViewHolder.SampleOnListener {
private SampleOnClickedListener mCallback;
public interface SampleOnClickedListener {
void sampleOnClicked(int position);
}
public MainFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_main, container, false);
SampleAdapter sampleAdapter = new SampleAdapter(this);
RecyclerView recyclerView = view.findViewById(R.id.sampleRecyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(view.getContext()));
recyclerView.setAdapter(sampleAdapter);
return view;
}
#Override
public void sampleOnClick(int position) {
Log.e("TAG", " from fragment / position:" + position);
mCallback.sampleOnClicked(position);
}
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
this.createCallbackToParentActivity();
}
private void createCallbackToParentActivity() {
try {
mCallback = (SampleOnClickedListener) getActivity();
} catch (ClassCastException e) {
throw new ClassCastException(e.toString() + " must implement OnButtonClickedListener");
}
}
}
Adapter:
public class SampleAdapter extends RecyclerView.Adapter<SampleAdapter.SampleRecyclerViewHolder> {
private final SampleRecyclerViewHolder.SampleOnListener mListener;
public SampleAdapter(SampleRecyclerViewHolder.SampleOnListener listener) {
this.mListener = listener;
}
#NonNull
#Override
public SampleRecyclerViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
return new SampleRecyclerViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull SampleRecyclerViewHolder holder, int position) {
holder.itemView.setOnClickListener(v -> mListener.sampleOnClick(holder.getBindingAdapterPosition()));
}
#Override
public int getItemCount() {
// modify with real size
return 0;
}
public static class SampleRecyclerViewHolder extends RecyclerView.ViewHolder {
public SampleRecyclerViewHolder(#NonNull View itemView) {
super(itemView);
}
public interface SampleOnListener {
void sampleOnClick(int position);
}
}
}
If I'm not mistaken, the result of the click can be retrieved in the activity as well as in the fragment and that's what I needed.
I'm testing a function of listening the AlertDialog button click (Positive & Neutral) in each ViewPager fragment. And I am using the Interface method right now but getting some troubles.
So the structure is like this, I have an AlertDialog created by DialogFragment, and I put a ViewPager with two fragments into this DialogFragment. My goal is, when I click on the Positive button on the AlertDialog, I want some methods inside those two ViewPager fragments get called so that I can collect the data on those fragments.
Now the problem is, only the second fragment responses, I don't know why.
Here are the codes:
I created an Interface file
public interface Communicator {
void onConfirmClick();
}
I have a DialogFragment
public class MainDialogFragment extends DialogFragment {
View dialogLayout;
Communicator communicator;
#Override
public void onAttachFragment(Fragment childFragment) {
communicator = (Communicator) childFragment;
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return dialogLayout;
}
#NonNull
#Override
public Dialog onCreateDialog(#Nullable Bundle savedInstanceState) {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity());
dialogLayout = getActivity().getLayoutInflater().inflate(R.layout.dialog_main, null);
ViewPager viewPager = dialogLayout.findViewById(R.id.main_viewPager);
final MainPagerAdapter adapter = new MainPagerAdapter(getChildFragmentManager());
viewPager.setAdapter(adapter);
dialogBuilder.setView(dialogLayout);
dialogBuilder.setPositiveButton("Confirm", null);
dialogBuilder.setNegativeButton("Cancel", null);
dialogBuilder.setNeutralButton("Change", null);
final AlertDialog dialog = dialogBuilder.create();
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
#Override
public void onShow(DialogInterface dialogInterface) {
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
communicator.onConfirmClick();
}
});
dialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Toast.makeText(view.getContext(), "Change click!!", Toast.LENGTH_SHORT).show();
}
});
}
});
return dialog;
}
}
My fragment A
public class MainFragmentA extends Fragment implements Communicator{
View rootView;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
rootView = inflater.inflate(R.layout.fragment_a, container, false);
return rootView;
}
#Override
public void onConfirmClick() {
Toast.makeText(getContext(), "Fragment A!!", Toast.LENGTH_SHORT).show();
}
}
My fragment B
public class MainFragmentB extends Fragment implements Communicator{
View rootView;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
rootView = inflater.inflate(R.layout.fragment_b, container, false);
return rootView;
}
#Override
public void onConfirmClick() {
Toast.makeText(getContext(), "Fragment B!!", Toast.LENGTH_SHORT).show();
}
}
My ViewPager adapter used inside DialogFragment
public class MainPagerAdapter extends FragmentPagerAdapter {
public MainPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new MainFragmentA();
case 1:
return new MainFragmentB();
default:
throw new IllegalArgumentException("Wrong position!!");
}
}
#Override
public int getCount() {
return 2;
}
}
My MainActivity
public class MainActivity extends AppCompatActivity{
Button showDialogButton;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
showDialogButton = findViewById(R.id.main_show_dialog_button);
showDialogButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
MainDialogFragment mainDialogFragment = new MainDialogFragment();
mainDialogFragment.show(getSupportFragmentManager(), "mainDialogFragment");
}
});
}
}
Anyone can help? I'll so appreciate that!!!
Use a collection of some sort of Communicator interfaces instead of a single one. You're overwriting the communicator every time a child fragment is attached.
public class MainDialogFragment extends DialogFragment {
View dialogLayout;
List<Communicator> communicators = new ArrayList<>();
#Override
public void onAttachFragment(Fragment childFragment) {
communicators.add((Communicator) childFragment);
}
// all the other things from the MainDialogFragment...
}
And in the BUTTON_POSITIVE callback iterate through the list.
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
for (Communicator communicator : communicators) {
communicator.onConfirmClick();
}
}
});
Only the communicator from the second fragment works
This is because you have two different instances of communicator in each of your fragments. As you are setting up the ViewPager, the second fragment is the last one that is being attached with the parent fragment. Hence, the communicator that you are initializing inside the onAttachFragment function of your MainDialogFragment class, is storing the reference from the second fragment only as this was the last one to be attached here.
In your case, I would rather suggest a very simple implementation using the lifecycle functions of the Fragment. Just take a public static variable in your MainDialogFragment class which will indicate if the okay button was clicked or not. And then check the value of that variable from each of your Fragment class inside the onResume function and perform the tasks accordingly. To get an idea of the implementation, please check the following.
Get a variable in your MainDialogFragment class like the following.
public static boolean isDialogOkayClicked = false; // Default false
Now in your MainFragmentA, implement the onResume function and check the value from the MainDialogFragment. Take the actions accordingly.
public class MainFragmentA extends Fragment implements Communicator{
View rootView;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
rootView = inflater.inflate(R.layout.fragment_a, container, false);
return rootView;
}
#Override
protected void onResume() {
super.onResume();
if(MainDialogFragment.isDialogOkayClicked)
doSomething();
}
#Override
public void onConfirmClick() {
Toast.makeText(getContext(), "Fragment A!!", Toast.LENGTH_SHORT).show();
}
}
Do the same thing for your other fragment.
Hope that helps!
I am working on a NavigationDrawer Activity.I also have a tabbed fragment which is opened by a navigation. I have a RecyclerView in one of the tabs.I have set the views of the RecyclerView to listen to clicks.
In the MainActivity of the NavigationDrawer I populate the BottomSheet so that it is visible throughout the navigations. And I have a textView in the Bottomsheet.
Now I want to change the contents of the textView(in the Bottomsheet) the title/description of the of the view clicked from the RecyclerView(or ViewHolder).
Populating my BottomSheet at MainActivity.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
extras=getIntent().getExtras();
nameholder=findViewById(R.id.nameolder);
ConstraintLayout llBottomSheet = findViewById(R.id.bottom_sheet);
bottomSheetBehavior = BottomSheetBehavior.from(llBottomSheet);
bottomSheetBehavior.setPeekHeight(250);
bottomSheetBehavior.setHideable(false);
bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback(){
#Override
public void onStateChanged(#NonNull View bottomSheet, int newState{}
#Override
public void onSlide(#NonNull View bottomSheet, float slideOffset) {
}
});
I made the viewHolder able to listen clicks.
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
public TextView name;
public TextView description;
public ViewHolder(#NonNull View itemView) {
super(itemView);
itemView.setOnClickListener(this);
name=itemView.findViewById(R.id.title);
description=itemView.findViewById(R.id.description);
}
#Override
public void onClick(View v) {
int position=getAdapterPosition();
ListItem item=mListItems.get(position);
Toast.makeText(mContext,"this is",Toast.LENGTH_LONG).show();
Intent intent=new Intent(mContext, MainActivity.class);
intent.putExtra("name",item.getName());
intent.putExtra("description",item.getDescription());
mActivity.nameholder.setText(item.getName());
mContext.startActivity(intent);
}
}
this is my Tabbed Fragment.
public class FragmentTabLayoutLibrary extends Fragment {
public FragmentTabLayoutLibrary() {}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view=inflater.inflate(R.layout.fragment_library, container, false);
ViewPager vp_pages=view.findViewById(R.id.vp_pages);
PagerAdapter pagerAdapter=new FragmentAdapter(getFragmentManager());
vp_pages.setAdapter(pagerAdapter);
TabLayout tbl_pages= view.findViewById(R.id.tbl_pages);
tbl_pages.setupWithViewPager(vp_pages);
return view;
}
class FragmentAdapter extends FragmentPagerAdapter {
public FragmentAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position){
case 0:
return new SongsListRecyclerview();
case 1:
return new HomeFragment();
}
return null;
}
#Override
public int getCount() {
return 2;
}
#Override
public CharSequence getPageTitle(int position) {
switch (position){
case 0:return "Recycler";
case 1:return "Home";
default:return null;
}
}
}
}
My RecyclerView is in the Recycler tab.
Please help anyone.I don't need code.I just want a way to trigger the Bottomsheet from the RecyclerView.Since the RecyclerView is not in the same main activity I don't know how to do this
As I understood you have a Fragment displaying RecyclerView items.And this fragment is hosted in the MainActivity which is controlling the BottomSheet.
1- You have to create an Interface to achieve the communication from SongsListRecyclerview to MainActivity as in here
let us say that the interface that you created look like:
interface OnSongSelectedListener{
void onSongSelected(String title, String content);
}
2- Once you have a reference of your listener from step 1 in your SongsListRecyclerview Fragment, Pass this listener to the adapter and in item click call the listener.
So your onClick will look like:
#Override
public void onClick(View v) {
int position=getAdapterPosition();
ListItem item=mListItems.get(position);
Toast.makeText(mContext,"this is",Toast.LENGTH_LONG).show();
onSongSelectedListener.onSongSelected(item.getName(),item.getDescription());
}
3- in your MainActivity where you implement OnSongSelectedListener
you will do the change
class MainActivity extends Activity{
...
#Override
public void onSongSelected(String title, String content){
bottomSheet.titleView.setText(title);
bottomSheet.contentView.setText(content);
}
}
I have an Weather adapter to show the weather, the activity is split into two screens, one half a listview (different adapter), the other is tablayout with another tablayout inside to scroll through all the different pages of weather that are available
After turning off the wifi to make sure i don't receive anything and then turning on the wifi to receive the weather i get the update method called for the adapter and the list is updated, new fragments created, onViewCreated is called but the fragments display as blank and as only 1 (can't cycle through them if there are multiple).
Here is the Weather ViewPagerAdapter
public class WeatherViewPagerAdapter extends FragmentStatePagerAdapter {
private List<Fragment> fragments;
public WeatherViewPagerAdapter(FragmentManager fm) {
super(fm);
fragments = new LinkedList<>();
if (Storage.getInstance().getWeatherModels().size() == 0){
// Displays page but with "no weather available" message
fragments.add(WeatherFragment.newInstance(null));
}else{
for (CurrentWeatherModel model : Storage.getInstance().getWeatherModels()){
fragments.add(WeatherFragment.newInstance(model));
}
}
}
public void rediscoverWeather(){
fragments.clear();
if (Storage.getInstance().getWeatherModels().size() == 0){
fragments.add(WeatherFragment.newInstance(null));
}else{
for (CurrentWeatherModel model : Storage.getInstance().getWeatherModels()){
fragments.add(WeatherFragment.newInstance(model));
}
}
}
#Override
public int getItemPosition(#NonNull Object object) {
return POSITION_NONE;
}
#Override
public int getCount() {
return fragments.size();
}
#Override
public Fragment getItem(int position) {
return fragments.get(position);
}
#Override
public Parcelable saveState() {
return null;
}
}
And this is the fragment that is in the Tablayout on the side that displays the weather
public class WeatherViewPagerFragment extends Fragment {
private WeatherViewPagerAdapter adapter;
private ViewPager viewpager;
private TabLayout tabLayout;
public static WeatherViewPagerFragment newInstance(){
return new WeatherViewPagerFragment();
}
#Override
public UniqueInfo getUniqueInfo() {
return null;
}
#Override
public void update() {
adapter.rediscoverWeather();
setPage(0);
adapter.notifyDataSetChanged();
}
public void setPage(int pageNumber){
if (pageNumber >=0 && pageNumber < adapter.getCount()){
viewpager.setCurrentItem(pageNumber, true);
}
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.weather_view_pager, container, false);
}
private void createAdapter(){
adapter = new WeatherViewPagerAdapter(getChildFragmentManager());
viewpager.setAdapter(adapter);
tabLayout.setupWithViewPager(viewpager);
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
viewpager = view.findViewById(R.id.viewpager);
tabLayout = view.findViewById(R.id.sliding_tabs);
createAdapter();
}
}
The update() for WeatherViewPagerFragment gets called when its meant to, which updates the Adapter, re-doing the fragments and calling onCreateView and onViewCreated, but what i see is just a blank fragment, any suggestions why this would be happening? i figured it could be a focus issue but i haven't had any luck as of yet with fixing it
I'm trying out Otto on Android and i'm trying to send back a message from my Fragment to the Activity. Here's the basics of my code:
My Bus provider:
public final class BusProvider {
private static final Bus mInstance = new Bus();
private BusProvider() {}
public static Bus getBusProviderInstance() {
return mInstance;
}
}
My Activity has the following code:
public class MyActivity
extends BaseActivity {
// ....
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
BusProvider.getBusProviderInstance().register(this);
// ....
}
#OnClick(R.id.profile_image)
public void onImageClicked() {
// ...
MyFragment fragment = MyFragment.newInstance(choices);
fragment.show(getFragmentManager(), "myChoices");
}
#Subscribe
public void onButtonChoicePicked(MyFragment.ChoicePickedEvent event) {
Toast.makeText(this, "reaching here", Toast.LENGTH_SHORT).show();
}
#Override
protected void onStop() {
super.onStop();
BusProvider.getBusProviderInstance().unregister(this);
}
// ...
}
and these are the important bits of code from my Fragment:
public class MyFragment
extends BaseDialogFragment {
// ...
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
LinearLayout layout = (LinearLayout) inflater.inflate(R.layout.dialog_choices,
container,
false);
setupDialogButtons(inflater, layout);
return layout;
}
private void setupDialogButtons(LayoutInflater inflater, LinearLayout parentView) {
ChoiceButtonViewHolder holder;
holder = new ChoiceButtonViewHolder(inflater, parentView);
holder.populateContent("First Choice", 1);
parentView.addView(holder.mChoiceTextView);
}
class ChoiceButtonViewHolder {
#InjectView(R.id.item_dialog_choice_desc) TextView mChoiceTextView;
private int mPosition;
ChoiceButtonViewHolder(LayoutInflater inflater, ViewGroup container) {
TextView mChoiceTextView = (TextView) inflater.inflate(R.layout.item_dialog_choice, container, false);
ButterKnife.inject(this, mChoiceTextView);
}
public void populateContent(String choiceDesc, int position) {
mChoiceTextView.setText(choiceDesc);
mPosition = position;
}
#OnClick(R.id.item_dialog_choice_desc)
public void onChoiceClicked() {
MyFragment.this.mDialog.dismiss();
BusProvider.getBusProviderInstance().post(new ChoicePickedEvent(1));
}
}
public static class ChoicePickedEvent {
public int mPositionClicked;
ChoicePickedEvent(int position) {
mPositionClicked = position;
}
}
}
I don't get any errors. But when i click my button from the fragment, the event onButtonChoicePicked doesn't get called.
Am I doing something wrong?
Am i misunderstanding how Otto works?
Is it a weird combination of ButterKnife and Otto that makes it not work?
Make sure you are importing "com.squareup.otto.Subscribe" not "com.google.common.eventbus.Subscribe"
The example code works without any issues independently. The reason i was facing this problem initially (as was rightly pointed out by #powerj1984): There was a misconfiguration in my project, where the bus that was being injected (via Dagger) was different from the bus instance that was being subscribed to for updates :P.
Lesson learnt: make sure the bus you use, is the same instance in both cases.