How to refresh RecyclerView of hidden fragment? - android

I have two Fragments A and B with RecyclerView list.
When fragment A is active I call:
fragmentTransaction.hide(Frag_A);
and I add fragment B with command:
fragmentTransaction.add(R.id.fragment_container, Frag_B);
When fragment B is active and I do some changes I need to silently call:
mAdapter.notifyDataSetChanged();
on Fragment A.
So it is possible to call this, when fragment is hidden?
Now I am using
#Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
}
But this method is triggered when I am going back from fragment B to A.
I want to refresh fragment A when fragment B is active. (User see fragment B list).
How to do that?

From my understanding, you want to refresh data in FragmentA while FragmentB is active.
First, you have create an Interface
interface FragmentCallback {
fun onResumeFragment()
}
Next, before you want to go to FragmentA, you are using add method. So, you have to add a tagfor FragmentA.
private fun loadFragmentA(someId: Int) {
val bun = Bundle()
bun.apply {
putInt(Constant.SOME_ID, someId)
}
val fragmentA = FragmentA()
fragmentA.arguments = bun
val fragmentManager = supportFragmentManager
val fragmentTransaction: FragmentTransaction =
fragmentManager.beginTransaction()
fragmentTransaction.add(
R.id.fragment_container,
fragmentA,
"TagFragmentA"
)
fragmentTransaction.commit()
}
At the MainActivity, you need to implement the interface that we created. inside the onResumeFragment method, we have to find that FragmentA by using tag. So, we can use the method inside that fragment.
class MainActivity : AppCompatActivity(), FragmentCallback {
override fun onResumeFragment() {
val fragmentA =
supportFragmentManager.findFragmentByTag("TagFragmentA") as FragmentA
fragmentA.refreshData()
}
}
Inside FragmentA, create refreshData method and make it public. So inside this method, you can notify the data changed.
fun refreshData(){
viewModel.getListSomething()
}
Inside FragmentB, you need to override the onAttach method to make the object for FragmentCallback.
private var fragmentCallback: FragmentCallback? = null
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is FragmentCallback) {
fragmentCallback = context
} else {
throw RuntimeException(
context.javaClass.simpleName
.toString() + " must implement FragmentCallback"
)
}
}
Finally create a method to trigger when there is data changes.
private fun timeToUpdateDataAtFragmentA(){
fragmentCallback?.onResumeFragment()
}
When you from FragmentB, back press to FragmentA, the data will update at the FragmentA.

Related

Parent Fragment communication with fragment inside viewpager

I have one MainActivity with -> FragmentA which contains -> ViewPager with (Fragment1,Fragment2,Fragment3)
Now in FragmentA I have one spinner and any selection must reflect the changes inside viewpager's currently visible fragment.
How can I achieve that? I don't want to follow ViewModel or EventBus approach for now as I am working on very old project. I want to use interface to communicate between them.
Create an interface inside your FragmentA
interface OnSpinnerValue{
fun onSpinnerValueChanged()
}
Create a WeakReference for the current selected fragment
private var _currentPage: WeakReference<OnSpinnerValue>? = null
private val currentPage
get() = _currentPage?.get()
fun setCurrentPage(page: OnSpinnerValue) {
_currentPage = WeakReference(page)
}
Now implement this interface in every child fragment of ViewPager
class Fragment1() : Fragment(), OnAddEvent {
override fun onSpinnerValueChanged() {
// implement your method
}
}
And, update currentPage value of the FragmentA, according to the selected fragment, and update it in onResume() of each child fragment
override fun onResume() {
super.onResume()
(parentFragment as FragmentA).setCurrentPage(this)
}
Now, trigger onSpinnerValueChanged from your spinner's onItemSelected methods
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
currentPage?.onSpinnerValueChanged()
}
It is strange if you don't want to use ViewModel ,
I know may be it is not best solutions but you can :
you can create function itIsYourFunctionToUpdate() inside your fragment and update him on FragmentA() , calling function with her object like fragmentB.itIsYourFunctionToUpdate()
or
if (ViewPager.getCurrentItem() == 0 && page != null) {
((FragmentClass1)page).itIsYourFunctionToUpdate("new item");
}
Also you can update viewPager like this mAdapter.notifyDataSetChanged() and all fragment inside viewPager must be updated

Fragment popped call OnViewCreated after PopBackStack

i have 1 Activity with 3 Fragments. (A, B and C). So,
Activity -> FragmentContainerView with fragment A
<androidx.fragment.app.FragmentContainerView
android:id="#+id/host_fragment"
android:name="cl.gersard.shoppingtracking.ui.product.list.ListProductsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:tag="ListProductsFragment" />
Fragment A has a button to go to Fragment B
Fragment A -> Fragment B (with addToBackStack)
Then, i go to from Fragment B to Fragment C
Fragment B -> Fragment C (without addToBackStack)
i need when i save a item in Fragment C, come back to Fragment A, so i dont use addToBackStack.
The problem is when in Fragment C i use
requireActivity().supportFragmentManager.popBackStack()
or
requireActivity().onBackPressed()
the Fragment A appears but the method OnViewCreated in Fragment C is called so execute a validations that i have in that Fragment C.
I need from Fragment C come back to Fragment A without calling OnViewCreated of Fragment C
Code of interest
MainActivity
fun changeFragment(fragment: Fragment, addToBackStack: Boolean) {
val transaction = supportFragmentManager.beginTransaction()
.replace(R.id.host_fragment, fragment,fragment::class.java.simpleName)
if (addToBackStack) transaction.addToBackStack(null)
transaction.commit()
}
Fragment A (ListProductsFragment)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupRecyclerView()
observeLoading()
observeProducts()
viewModel.fetchProducts()
viewBinding.btnEmptyProducts.setOnClickListener { viewModel.fetchProducts() }
viewBinding.fabAddPurchase.setOnClickListener { addPurchase() }
}
private fun addPurchase() {
(requireActivity() as MainActivity).changeFragment(ScanFragment.newInstance(),true)
}
Fragment B (ScanFragment)
override fun barcodeDetected(barcode: String) {
if (processingBarcode.compareAndSet(false, true)) {
(requireActivity() as MainActivity).changeFragment(PurchaseFragment.newInstance(barcode), false)
}
}
Fragment C (PurchaseFragment)
private fun observePurchaseState() {
viewModel.purchasesSaveState.observe(viewLifecycleOwner, { purchaseState ->
when (purchaseState) {
is PurchaseSaveState.Error -> TODO()
is PurchaseSaveState.Loading -> manageProgress(purchaseState.isLoading)
PurchaseSaveState.Success -> {
Toast.makeText(requireActivity(), getString(R.string.purchase_saved_successfully), Toast.LENGTH_SHORT).show()
requireActivity().supportFragmentManager.popBackStack()
}
}
})
}
The full code is here https://github.com/gersard/PurchaseTracking
OK, I think I see your issue. You are conditionally adding things to the backstack which put the fragment manager in a weird state.
Issue
You start on Main, add the Scanner to the back stack, but not the Product. So when you press back, you're popping the Scanner off the stack but the Product stays around in the FragmentManager. This is why get a new instance each and every time you scan and go back. Why this is happening is not clear to me - seems like maybe an Android bug? You are replacing fragments so it's odd that extra instances are building up.
One Solution
Change your changeFragment implementation to conditionally pop the stack instead of conditionally adding things to it.
fun changeFragment(fragment: Fragment, popStack: Boolean) {
if (keepStack) supportFragmentManager.popBackStack()
val transaction = supportFragmentManager.beginTransaction()
.replace(R.id.host_fragment, fragment,fragment::class.java.simpleName)
transaction.addToBackStack(null) // Always add the new fragment so "back" works
transaction.commit()
}
Then invert your current logic that calls changeFragment:
private fun addPurchase() {
// Pass false to not pop the main activity
(requireActivity() as MainActivity)
.changeFragment(ScanFragment.newInstance(), false)
}
And ...
override fun barcodeDetected(barcode: String) {
if (processingBarcode.compareAndSet(false, true)) {
// Pass true to pop the barcode that's currently there
(requireActivity() as MainActivity)
.changeFragment(PurchaseFragment.newInstance(barcode), true)
}
}

How can I start an activity with specific fragment

In my main activity I have bottom navigation bar. Each button opens a different fragment.My code in main activity looks like this
class MainActivity : AppCompatActivity() {
private val homeFragment = HomeFragment()
private val calendarFragment = CalendarFragment()
private val addFragment = AddFragment()
private val plannerFragment = PlannerFragment()
private val profileFragment = ProfileFragment()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
replaceFragment(homeFragment)
nav_view.setOnNavigationItemSelectedListener{
when(it.itemId){
R.id.homeButton -> replaceFragment(homeFragment)
R.id.calendarButton -> replaceFragment(calendarFragment)
R.id.addButton -> replaceFragment(addFragment)
R.id.plannerButton -> replaceFragment(plannerFragment)
R.id.profileButton -> replaceFragment(profileFragment)
}
true
}
}
private fun replaceFragment(fragment: Fragment){
if (fragment!=null){
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragmentContainer, fragment)
transaction.commit()
}
}
}
In profileFragment I have a button that opens a new activity called EditProfile. In that activity I have a button called goBackToProfileButton
I want to set a listener that will go back to mainactivity, but I want profileFragment to be open not the default fragment which is homeFragment.
goBackToProfileButton.setOnClickListener {
val intent = Intent(this,MainActivity::class.java)
startActivity(intent)
}
For now my code looks like this
You need to add some bundle data to your intent with information which fragment should be started
https://stackoverflow.com/a/819427/11538132
And then when you receive this bundle data then you can manually set your ** profileFragment**
You can add TAG while fragment transaction.
private fun replaceFragment(fragment: Fragment){
if (fragment!=null){
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragmentContainer, fragment, "FragmentTag")
transaction.commit()
}
}
While calling the first time to replace a fragment, find your fragment by tag.
//replaceFragment(homeFragment)
val fragment = supportFragmentManager.findFragmentByTag("FragmentTag")
if(fragment !=null){
replaceFragment(fragment)
} else{
replaceFragment(homeFragment)
}
if findFragmentByTag return null means no fragment was added. So add Home Fragment. If not null , it will update with last fragment added.
You could use fragment.startActivityForResult() and call the ProfileFragment from the MainActivity once it receives the onActivityResult() callback.

How to return previous fragment by calling activity's onBackPress()

I have an activity with two fragments added to it using :
fun addFragment(fragment: Fragment){
private val fragmentManager = supportFragmentManager
val fragmentTag = fragment::class.simpleName.toString()
val ft = fragmentManager.beginTransaction()
ft.add(R.id.fragments_container, fragment,fragmentTag)
ft.addToBackStack(fragmentTag)
ft.commit()
}
I add two fragments A and B to activity with this order:
A ---> B
when i press back button on phone it return from B to A as expected
but the problem is that when i call activity's onBackPressed method when clicking on a view for example:
imgBack.setOnClickListener {
onBackPressed()
}
it does not work like when i press back button on the phone
it returns to fragment A but not showing fragment A views as expected.
onBackPressed:
override fun onBackPressed() {
if (fragmentManager.backStackEntryCount > 1) {
fragmentManager.popBackStack()
} else {
finish()
}
}
if you imgBack are in fragmentB call
imgBack.setOnClickListener {
activity?.onBackPressed()
}

How to refresh fragment it is added to viewpager, when I press back button in another fragment(it is not in viewpager)

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

Categories

Resources