Navigation in a recycler view between fragments - android

I am making a simple app with a recycler containing a list of items, and when an item is clicked, it needs to navigate to a new separate page for that item.(Note that the recycler view with list of items and the item page are both fragments and the navigation needs to happen without starting a new activity)
I have set up the NavHostFragment in MainActivity and the onClickListener to get the item clicked in the onBindViewHolder method of Adapter classs of the fragment with recycler view. I cannot figure out how to call the navController in MainACtivity within the onClickListener method in Adapter class to navigate to the item fragment, or is there any other way to do this? I am newbie android studio dev! Please help, thanks!

You can simply implement an interface to make communication between fragment and adapter.
Suppose there is FragmentA, FragmentB, and an Adapter.
FragmentA and adapter are related and you want to move from FragmentA to FragmentB. So you connect FragmentA with the adapter using an interface listener. Handle the click event in the adapter and pass data from adapter to FragmentA and from override method in FragmentA call FragmentB using nav controller.
Here is simple example:
Listener:
public interface eventListener { void onEvent(int key, Object object); }
FragmentA:
class FragmentA : Fragment(), eventListener {
override fun onEvent(key: Int, any: Any?) { // call FragmentB }
}
Adapter:
class Adapter(listener: WoovlyEventListener) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.btn.setOnClickListener{ listener.onEvent(0, null) }
}
}
FragmentB:
class FragmentB : Fragment(){}

Related

How can we achieve Shared View Model communication between a Fragment and Activity, where the Activity is not the Parent

I am trying to achieve Fragment to Activity communication given that the Activity is not the parent Activity.
So, I have a MainActivity that has a fragment called ContactListFragment, while the add button on the BottomNavigationView of MainActivity is clicked, I am opening another AddContactActivity to add a contact. My requirement is to when I am clicking the save button on the AddContactActivity, I need to initiate a data-sync with server in ContactListFragment. I think the best approach would be to use a Shared View Model for this, but in that Case how should I create the view model so that the lifecycle owner doesn't get changed?
I thought about using the Application Context as the owner but I feel like its an overkill for a task like this, and it may produce some consequences down the line when other modules are added to the project.
So is there a way to efficiently implement this approach? Thanks.
Write an interface class with object/objects/Data types you need to sync
interface OnSaveClickListener {
fun onSaveClicked(contact: Contact)
}
Now in ContactListFragment class
class ContactListFragment : Fragment(), OnSaveClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
(activity as AddContactACtivity).mOnSaveClickListener = this
}
override fun onSaveClicked(contact: Contact) {
// Whatever you want to do with the data
}
}
In AddContactActivity,
class AddContactActivity {
var mOnSaveClickListener : OnSaveClickListener? = null
private void whenYouClickSave(contact: Contact){
mOnSaveClickListener?.onSaveClicked(contact)
}

Android - Communication between two Fragments via a shared ViewModel

I have a Fragment A that displays a list of items. When one item is clicked we navigate to a Fragment B that shows a more detailed view on the selected item. So, I needed some sort of communication between Fragment A & B.
In the docs (see here ) they state that a shared ViewModel for communication between Fragments is the recommended way. So, implement it like this:
class SharedViewModel : ViewModel() {
// used to share Item between list and detail view
private val _selectedItem = MutableLiveData<Item>()
// Fragment B listens for this LiveData variable
val selectedItem : LiveData<Item> = _selectedItem
// called by the Fragment A which passes the selected item as argument
fun selectItem(item: Item) {
_selectedItem.value = item
}
}
In Fragment B, we listen for the selectedItem LiveData variable like this:
....
// I use a global variable here cuz I want to use it in several different methods
private lateinit var selectedItem : Item
....
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// listen for the selected item
sharedViewModel.selectedItem.observe(viewLifecycleOwner, Observer { item ->
selectedItem = item
})
}
Now, in onStart() I have some operations that can only operate when selectedItem is initialized:
override fun onStart() {
super.onStart()
doSomethingWithSelectedItem(selectedItem) //<--- CRASH
doAnotherThingWithSelectedItem(selectedItem) // probably it would crash here, too
}
At this point, I get the exception that the selectedItem lateinit var variable is not initialized. So, I assume that at this point the setting in onViewCreated() (which is selectedItem = item) is not kicked in, yet.
So, my question is: Which lifecycle method is the best when you want to initialize a lateinit variable whose value is shared via a ViewModel between two Fragments ?
I think that the problem is that the function inside the observe() method of LiveData is not executed instantly. OnViewCreated() is a method that is executed previously to onStart(). If the LiveData observe() method executed the function instantly you wouldn't have any problem.
What the observe() method does is to set the LiveData object to observe to a change to its value without executing the defined function instantly.
What I would do is writting this piece of code in Fragment B:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
selectedItem = sharedViewModel.selectedItem.value
}
In your case I think that you don't even need to use a LiveData object because you only want to get a value and this value won't change as long as you are in the same fragment.
onStart() lifecycle of the fragment is called prior to the onViewCreated()
lifecycle of the fragment.
observe the livedata in onstart and set the lateinit variable. And depends on what ur function do, u can call them in onStart() after lateinit variable is set or in onViewCreated()

How to avoid duplicate code in the activity and fragment?

When an activity is launched, the condition is checked in it, if the condition is true, the openNewActivity method is called and the current activity ends. But if the condition is false, then a transition is made to the fragment, inside which this condition can become true and then you need to call the openNewActivity method, which is defined in the activity. I do not want to duplicate this method in the fragment, how to properly implement a call to this method from the fragment? What are the best practices in such cases?
Activity
class FirstActivity : AppCompatActivity(), MyInterface {
override fun onSomethingDone() { //This function gets called when the condition is true
openNewActivity()
}
override fun onAttachFragment(fragment: Fragment) { //A fragment MUST never know it's an activity, so we exposed fragment interface member to easily initialize it whenever the fragment is attached to the activity.
when (fragment) {
is MyFragment -> fragment.myInterface = this
}
}
override fun onCreate() {
super.onCreate()
}
private fun openNewActivity() {
//opens a new activity
}
}
Interface
interface MyInterface {
fun onSomethingDone()
}
Fragment
class MyFragment : Fragment() {
var myInterface: MyInterface? = null
override fun onCreate() {
if (somethingIsTrue)
myInterface.onSomethingDone() //condition is true, call the interface method to inform the activity that the condition is true and the new activity should be opened.
}
}
Create an interface. Initialize the fragment's interface in the activity's onAttachFragment for the reason mentioned in the code. This way, the function for starting a new activity is defined only in the activity and does not need to be duplicated in the fragment.

Get Data in Fragment through Retrofit

In this problem I want to get Data from My API
In which I get Data from Retrofit
I want to show data in RecyclerView in Fragment Tabs but how can I send data from activity to Fragment
This is all I have tried
Retrofit call which provide me ArrayList of my posts
getMainApp().swiftAPI.getPosts().enqueue(object : Callback<ArrayList<Post>>{
override fun onFailure(call: Call<ArrayList<Post>>?, t: Throwable?) {
Toast.makeText(this#DashboardActivity, t?.message, Toast.LENGTH_SHORT)
}
override fun onResponse(call: Call<ArrayList<Post>>?, response: Response<ArrayList<Post>>?) {
if (response?.isSuccessful!!){
}
}
PagesFragment
val rootView = inflater.inflate(R.layout.fragment_page, container, false)
val video_recyclerview = rootView.findViewById(R.id.pages_rcv) as RecyclerView // Add this
video_recyclerview.layoutManager = LinearLayoutManager(activity)
video_recyclerview.adapter = PagesAdapter()
return rootView
I want to Know if there is any way possible to send ArrayList to fragment cause my data is in ArrayList
You can define an interface in your activity and let the fragment implement the interface. You can follow this example on my github: ActivityToFragmentCommunication
Basically, in your activity define:
public interface DataLoadedListener {
public void onDataLoaded(ArrayList<Post> posts);
}
Then, make your fragment implement the interface like below:
public class ExampleFragment extends Fragment implements MainActivity.DataLoadedListener {
// your fragment code
}
Finally in the onCreate() method of your activity:
// Create new fragment and transaction
mExampleFragment = new ExampleFragment();
// setting mExampleFragment as data load listener
mDataLoadedListener = (DataLoadedListener) mExampleFragment;
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack if needed
transaction.replace(R.id.flContainer, mExampleFragment);
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();
// load data after click
btLoadData.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
loadData();
// notify attached fragment
mDataLoadedListener.onDataLoaded(myStrings);
}
});
First check currentFragment in your activity class. You will get the fragment reference object in your activity class . so the example will like below:
Suppose you have a fragment called DataFragment and you have a reference mDataFragment in your activity class. now when you get data in your activity class you will call ((DataFragment)mDataFragment).passData(yourDataList). Remember passData() is a public method in your fragment class. Then you can add data in adapter and call notifyDataSetChanged()
Since you are working on Android, I would recommend the ViewModel component which makes it really easy to communicate between a activity and it's fragments.
First add the package to your app
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
Then create a ViewModel class
public class MyViewModel extends ViewModel {
public MutableLiveData<ArrayList<Post>> posts = new MutableLiveData<ArrayList<Post>>();
}
Now in the fragment subscribe to it.
public class DetailFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.posts.observe(this, { posts ->
// Update the UI.
});
}
}
Then set the value in your MainActivity as shown below and voila you have the data in your fragment. You can read more about it here
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
// Create a ViewModel the first time the system calls an activity's onCreate() method.
// Re-created activities receive the same MyViewModel instance created by the first activity.
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
model.users.setValue(posts);
}
}

Fragment Backstack Toolbar title

Is there an efficient way to automatically set the toolbar's title when adding/replacing fragments as well as popping fragments from the backstack?
I have implemented this abstract method in my BaseFragment class:
abstract fun header() : String
override fun onResume() {
super.onResume()
(activity as SSBaseActivity).header.text = header()
}
and I modify the header in each Fragment that inherits from my BaseFragment class and displays the value in onResume but I noticed that when I press back, the last title set isn't being replaced from the fragment currently in the stack.
You could do this by using an OnBackStackChangedListener in your Activity:
supportFragmentManager.addOnBackStackChangedListener {
val fragment = supportFragmentManager.findFragmentById(R.id.yourFragmentFrame)
if (fragment is BaseFragment) {
header.text = fragment.header()
}
}

Categories

Resources