I am trying to send information from fragment to the main activity.
I am trying to set a var interfaceName in a fragment from the main activity.
I created var menuInterface: MenuInterface and tried to set it in onNavigationItemSelected using
myFragment.menuInterface = this
The menuInterface stays null for some reason... any idea why?
the onNavigationItemSelected
override fun onNavigationItemSelected(menuItem: MenuItem): Boolean {
when (menuItem.itemId) {
R.id.feedLayoutId -> {
feedFragment = FeedFragment()
feedFragment.menuInterface = this
barTitle.text = "myTitle"
supportFragmentManager
.beginTransaction()
.replace(R.id.frame_layout, feedFragment)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.commit()
}
}
drawerLayout.closeDrawer(GravityCompat.START)
return true
}
Implement MenuInterface in your activity,and remove this line from ur code.
feedFragment.menuInterface = this
In ur fragment:
private var menuInterface: MenuInterface? = null
override fun onAttach(context: Context?) {
super.onAttach(context)
menuInterface = context as MenuInterface
}
override fun onDetach() {
menuInterface = null
super.onDetach()
}
I have already provided solution for such question. https://stackoverflow.com/a/35038574/3027124
The idea is communication between activity and fragment should be done through interfaces or you should implement MVVM architecture and use the same view model for both fragment and activity.
Useful resources:
https://developer.android.com/training/basics/fragments/communicating
MVVM Overview https://developer.android.com/jetpack/docs/guide#overview
Related
I have recyclerView and after click of card I would like to replace fragments in activity. The problem is I have no access to activity. Here is my code in adapter:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val itemsViewModel = mList[position]
holder.tagImage.setImageResource(itemsViewModel.tagImage)
holder.tagName.text = itemsViewModel.tagName
holder.tagDescription.text = itemsViewModel.tagDescription
holder.itemView.setOnClickListener {
Log.d(InTorry.TAG, itemsViewModel.tagName)
val fragment = ProductsFragment()
val transaction = activity?.supportFragmentManager?.beginTransaction()
transaction?.replace(R.id.homeFragmentsContainer, fragment)
//transaction?.disallowAddToBackStack()
transaction?.commit()
}
}
The above replace code works in fragment but in adapter there is "activity?" error.
Kind Regards
Jack
There are multiple ways to solve this problem.
Using Context
You can use the context from holder.itemView and cast it into an Activity.
This is probably the simplest way, however this can be problematic since a Context may represent an Activity, a Service, an Application, etc. in which case it may lead to a ClassCastException when used simply.
Using Callback
You can set up a callback from your Adapter to your Activity or Fragment and then replace your Fragment.
Use JetPack Navigation
This is my personal favorite as the latest versions allow you to access NavController from Activity, Fragment or any View in the hierarchy to navigate. This is just one of many benefits of using this library.
Here is a link to Jetpack Navigation.
The Simplest and Safer way to solve this is my using Callback from your holder to activity. Below is the step by step process :
Decalare an Interface
interface OnItemClick {
fun onClick()
}
Implement that interface in you Activity and put the desired code
class MainActivity : OnItemClick {
...
override onClick() {
// Do whatever you want
val fragment = ProductsFragment()
val transaction = fragmentManager.beginTransaction()
transaction.replace(R.id.homeFragmentsContainer, fragment)
transaction.commit()
}
}
Create a variable of that Interface type in you Adapter and in your onBindViewHolder method invoke that interface
class MyAdapter(val listener : OnItemClick) {
...
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
...
listener.onClick()
}
Finally pass that interface to your Adapter from you Activity
class MainActivity : OnItemClick {
val adapter = MyAdapter(this)
...
}
NOTE : Please don't pass activity to context here and there you will get unexpected result and most probably a crash.
After watching this video https://www.youtube.com/watch?v=WqrpcWXBz14
I managed to do it this way
In Adapter
class TagsAdapter(var mList: List<TagsViewModel>) :
RecyclerView.Adapter<TagsAdapter.ViewHolder>() {
var onItemClick: ((TagsViewModel) -> Unit)? = null//click listener STEP 1!!!
override fun onBindViewHolder(holder: TagsAdapter.ViewHolder, position: Int) {
holder.itemView.setOnClickListener {
onItemClick?.invoke(itemsViewModel)//click listener STEP 2!!!
}
}
}
And in Fragment
class TagsFragment : Fragment() {
private lateinit var tagsRecyclerView: RecyclerView
private var tagsArray = ArrayList<TagsViewModel>()
private lateinit var adapter: TagsAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
adapter = TagsAdapter(tagsArray)
tagsRecyclerView.adapter = adapter
adapter.onItemClick = {//click listener STEP 3!!!
val fragment = ProductsFragment()
val transaction = activity?.supportFragmentManager?.beginTransaction()
transaction?.replace(R.id.homeFragmentsContainer, fragment)
//transaction?.disallowAddToBackStack()
transaction?.commit()
}
}
}
It looks very clean and easy. I don't know is a correct way but it works
I have the following method
// Change fragment extension function to handle navigation easily
fun changeFragment(fragmentManager: FragmentManager?, #IdRes containerId: Int, fragment: Fragment?, addToBackStack: Boolean = false) {
if (fragmentManager == null || fragment == null) return
val fragmentTransaction = fragmentManager.beginTransaction()
if (addToBackStack) fragmentTransaction.addToBackStack(null)
fragmentTransaction.replace(containerId, fragment, fragment::class.java.simpleName).commit()
}
and I am using it inside one of my fragments -
class InspectionFragment : Fragment(R.layout.fragment_inspection) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
inspectionAdapter = InspectionAdapter { inspection ->
changeFragment(
parentFragmentManager, R.id.fragment_inspection_frame_layout,
InspectionDetailsFragment.newInstance(inspection), true
)
}
}
}
class InspectionDetailsFragment : Fragment() {
companion object {
fun newInstance(inspection: Inspection): InspectionDetailsFragment {
val inspectionDetailsFragment = InspectionDetailsFragment()
val bundle = Bundle()
bundle.putParcelable(GeneralConstants.INSPECTION, inspection)
inspectionDetailsFragment.arguments = bundle
return inspectionDetailsFragment
}
}
}
The thing is that, when I instansiate onetop of the 'InspectionDetailsFragment' layer another fragment and press back, it goes directly back to the 'InspectionFragment' parent.
I can't understand why this is happening.
Anyone has an idea?
When you call changeFragment function, you're passing true as last argument:
changeFragment(
parentFragmentManager, R.id.fragment_inspection_frame_layout,
InspectionDetailsFragment.newInstance(inspection), true
)
In changeFragment function:
if (addToBackStack) fragmentTransaction.addToBackStack(null)
It doesn't get added to backStack. Instead of null i think you should pass String value as the name of the backStack you want to use.
I'm not 100% sure about this. You can read more about fragment transactions from here: https://developer.android.com/guide/fragments/transactions
I have created this extension function for navigating between fragments which is pretty straight forward but somehow it's not working. it's not doing anything and nothing changes when I click the button. I think the problem is with this#navigate argument but I don't see why that should be a problem.
fun Fragment.navigate(): Int? {
return fragmentManager?.run {
beginTransaction()
.replace(
R.id.my_container,
this#navigate,
this#navigate::class.simpleName
)
.commit()
}
}
and the usage is like this
class TestTwoFragment : Fragment(R.layout.fragment_test_two) {
....
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
...
goto_three.setOnClickListener {
TestThreeFragment
.newInstance()
.navigate()
}
}
You're using the fragmentManager of the new fragment which will be null if the fragment hasn't been added yet, like in your case. Since you're using ?.run, nothing happens and the method returns null.
Consider adding a fragment manager parameter to your method:
fun Fragment.navigate(fm: FragmentManager): Int? {
return fm.run {
beginTransaction()
.replace(
R.id.my_container,
this#navigate,
this#navigate::class.simpleName
)
.commit()
}
}
And then:
TestThreeFragment
.newInstance()
.navigate(getParentFragmentManager())
There is a huge bug I am trying to figure out in my latest project using Kotlin. So how the app works is that it has a Bottom Navigation Activity, and on click on the icons on the Bottom menu, it replaces the container with the respected fragment. On onCreate of the activity, I instantiate all the different fragments with a function defined in a companionObject within the fragments, which returns itself (kind of like a static factory method in java):
override fun onCreate(savedInstanceState: Bundle?) {
{...}
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
fragment1 = Fragment1.newInstance()
fragment2 = Fragment2.newInstance()
}
Then I have this switch to replace the container to the respected fragment on click:
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.f1 -> {
replaceFragment(fragment1)
return#OnNavigationItemSelectedListener true
}
R.id.f2 -> {
replaceFragment(fragment2)
return#OnNavigationItemSelectedListener true
}
}
false
}
{...}
fun replaceFragment(destFragment: Fragment) {
supportFragmentManager
.beginTransaction()
.replace(R.id.container, destFragment)
.addToBackStack(destFragment.toString())
.commit()
}
The fragments are communicating. If you where to click on both fragments, and thereby cause their lifecycles to execute, the code works fine. However, if you do changes in frag1, invoking a communication between the fragments, without having clicked on frag2 in advance, the app crashes. The reason is this line in frag2:
fun saveList(){
val sharedpref : SharedPreferences = context!!.getSharedPreferences("sharedPrefs", MODE_PRIVATE)
{...}
}
Turns out that the context is null, if the user has never invoked the frag2 lifecycle by clicking it. Any ideas to how to fix this problem?
Use Activity context for these case
fun saveList() {
activity?.let {
val sharedpref: SharedPreferences =
it.getSharedPreferences("sharedPrefs", MODE_PRIVATE)
{ }
}
}
How to refresh Fragment1 when I pressed back button in Fragment2.Here Fragement1 is added to View Pager inside Activity class. Both Fagments are under android.app.Fragment.
The way to go around this is via Fragment communication which involves the host Activity as the middleman for example :
interface FragmentCommunicator {
fun refreshFragmentOne()
}
class MyActivity : Activity(), FragmentCommunicator {
override fun refreshFragmentOne() {
val fragmentOne = pageAdapter.instantiateItem(pager,1) as? FragmentOne?
fragmentOne?.refresh()
}
}
class FragmentTwo : Fragment() {
var listener : FragmentCommunicator? = null
override fun onAttach(context: Context?) {
super.onAttach(context)
listener = context as FragmentCommunicator
}
override fun onDetach() {
listener = null
super.onDetach()
}
}
now in your Button#onClick call listener?.refreshFragmentOne()
for more details please read https://developer.android.com/training/basics/fragments/communicating.html
try to refresh fragment when it visible to user.
Override setUserVisibleHintmethod in your fragment1