Fragments UI's overlapping - android

I'm creating a shopping app where the user can choose next product from the fragment. That will open the next fragment of the same class with more products and so on. I want to use swipe to dismiss behaviour, so when I swipe my finger down I dissmiss the current fragment. It's essential that while swiping the current fragment the user will be able to see the fragment underneath, so I can only use add instead of replace. Here's the problem: When I use add in my fragment manager, the created fragments UI's overlap, which makes the user swipe multiple fragments at once and not show swiping animation. The fragment uses one xml layout so it shares id across all fragments so I have no way of controlling which layouts are disabled. How do I make the user only swipe one fragment/disable touching fragments underneath? The swipe to dissmis is working well with replace but that's not the point. Here's my code.
Activity:
class PullDismissActivity : AppCompatActivity(), PullDismissLayout.Listener {
private lateinit var viewModel: StackingFragmentsViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pulldismiss)
viewModel = ViewModelProvider(this).get(StackingFragmentsViewModel::class.java)
supportFragmentManager.beginTransaction()
.add(R.id.pull, SwipeFragment(), null)
.addToBackStack(null) // name can be null
.commit()
val layout = findViewById<PullDismissLayout>(R.id.pull)
layout.setListener(this)
}
override fun onDismissed() {
viewModel.popBackStack()
supportFragmentManager.popBackStack()
}
override fun onShouldInterceptTouchEvent(): Boolean {
return false
}
Fragment:
class SwipeFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val viewModel = ViewModelProvider(requireActivity()).get(StackingFragmentsViewModel::class.java)
viewModel.addFragment(this.hashCode())
val fragid = view.findViewById<TextView>(R.id.fragid)
fragid?.text = System.currentTimeMillis().toString()
val button = view.findViewById<Button>(R.id.button)
button.setOnClickListener {
requireActivity().supportFragmentManager.beginTransaction()
.add(R.id.pull, SwipeFragment(), null)
.addToBackStack(null)
.commit()
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment,container,false)
}

Related

call a fragment from another class\function kotlin

GameActivity
____FragmentQuest
____FragmentFight
class MapLvl.kt
this is a text RPG Fragment Quest displays a journey through different maps (changing the content in one fragment according to a template) text, pictures, navigation buttons. if there is a mob on the map, then the "Fight" button appears and a fragment of the turn-based fight FightFragment opens (hit the head \ legs\ body protect the head \ legs\ body). after the battle, return to QuestFragment
class MapLvl fills with the content of FightFragment
I need to change QuestFragment from classLvl to FightFragment. how to do it?
it doesn't work:
class MapLvl.kt:
class MapLevels(){
fun changeLvl (bind: FragmentQuestBinding,hero: Hero, activity: GameActivity,db: Maindb) {
when (hero.mapLvl) {
1 -> MapLevels().mapLevel1(bind, activity, hero, db)
2 -> MapLevels().mapLevel2(bind, activity, hero,db)
else -> {}
}
}
fun mapLevel2 (bind: FragmentQuestBinding,activity: GameActivity,hero:Hero,db: Maindb) {
bind.btnAtack.visibility= View.VISIBLE
//the problem is here:
bind.btnAtack.setOnClickListener {
(activity as GameActivity).supportFragmentManager
.beginTransaction()
.replace(R.id.placeHolder,FightFragment.newInstance())
.commit()
}
}
}
error: FragmentManager has not been attached to a host
QuestFragment:
class QuestFragment : Fragment() {
lateinit var bind:FragmentQuestBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
bind = FragmentQuestBinding.inflate(inflater)
return bind.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val hero = Hero()
val db = Maindb.heroSetDb(requireActivity())
hero.extractHeroData(db,hero)
scopeMain.launch {
delay(50)
MapLevels().changeLvl(bind,hero,GameActivity(),db)
}
if you make a call directly from a Fragment, then it works: (but it is necessary not from the fragment but from the class)
QuestFragment:
class QuestFragment : Fragment() {
lateinit var bind:FragmentQuestBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
bind = FragmentQuestBinding.inflate(inflater)
return bind.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val hero = Hero()
val db = Maindb.heroSetDb(requireActivity())
hero.extractHeroData(db,hero)
scopeMain.launch {
delay(50)
MapLevels().changeLvl(bind,hero,GameActivity(),db)
}
bind.btnAtack.setOnClickListener {
(activity as GameActivity).supportFragmentManager
.beginTransaction()
.replace(R.id.placeHolder,FightFragment.newInstance())
.commit()
}
it Work:
class MapLevels(val bind: FragmentQuestBinding,
val hero: Hero,
val db: Maindb,
val activity: FragmentActivity ){
fun changeLvl() {
when (hero.mapLvl) {
1 -> MapLevels(bind,hero,db,activity).mapLevel1()
2 -> MapLevels(bind,hero,db,activity).mapLevel2()
else -> {}
}
}
fun mapLevel2() {
bind.imgLocation.setImageResource(R.drawable.map_loc02)
bind.txtLocationDiscription.text ="text"
bind.btnAtack.visibility= View.VISIBLE
bind.btnAtack.setOnClickListener {
(activity as FragmentActivity).supportFragmentManager
.beginTransaction()
.replace(R.id.placeHolder,FightFragment.newInstance())
.commit()
MapLevels().changeLvl(bind,hero,GameActivity(),db)
It looks like you are creating a new GameActivity instance to pass to your changeLvl() method. YOU SHOULD NEVER DO THIS. The new activity is NOT the same one that currently is displayed on the screen. Instead, you should use the requireAcitivty() to get the fragment's parent activity:
MapLevels().changeLvl(bind,hero,requireActivity(),db)
I can't be sure this will fix your current problem because I'm not even sure what the problem is exactly. But it is one issue that you need to change.

How to run code just one time in Fragment on Android

In my application I have some fragments and for show this fragments I want use NavigationComponent.
I have one problem. When click on BottomNavigationItems and change fragments, run again fragment code!
I want just run codes just for first time!
My codes (for one of fragments) :
#AndroidEntryPoint
class HomeNewFragment : Fragment(), HomeContracts.View {
//Binding
private lateinit var binding: FragmentHomeNewBinding
#Inject
lateinit var presenter: HomePresenter
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentHomeNewBinding.inflate(layoutInflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//InitViews
binding.apply {
//Call apis
presenter.callApi()
}
}
}
For example when click on items, every time call this code : presenter.callApi()
Or when go to DetailFragment and when click on back, again call presenter.callApi()
How can I fix it?
You should replace your presenter.callApi() with fun onFragmentCreated triggered in your Presenter(ViewModel).
All you need is a boolean var to check its first time or not cause your Presenter(ViewModel) attatched to the activity, not the fragment so it can store your state.
Presenter {
fun onFragmentCreated() {
if(firstTime) callApi()

Main Activity go to background as a result of morphing animation

I have multiple Activities includes multiple fragments. I try to display morphing animation from a RecyclerView item in 1st fragment to an ImageView in 2nd fragment. 1st fragment go to background (it becomes a bit dark) and when I return back from 2nd fragment, when View lays out in its place in RecyclerView, 1st fragment comes to foreground (it becomes lighter). It looks like a blinking in whole screen.
In 1st Activity :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
setExitSharedElementCallback(
MaterialContainerTransformSharedElementCallback()
)
}
In 1st fragment :
override fun onClick(t: T, poster: View) {
val intent = Intent(activity, DetailActivity::class.java).apply {
putExtras(Bundle().apply {
putParcelable(EXTRA_TMDB_ITEM, t)
putParcelable(EXTRA_NAV_TYPE, navType)
})
}
val options = ActivityOptions.makeSceneTransitionAnimation(
activity,
poster, t.name
)
startActivity(intent, options.toBundle())
}
in 2nd Activity :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
// set up shared element transition
setEnterSharedElementCallback(
MaterialContainerTransformSharedElementCallback()
)
window.sharedElementEnterTransition = getContentTransform()
window.sharedElementReturnTransition = getContentTransform()
}
/** get a material container arc transform. */
private fun getContentTransform(): MaterialContainerTransform {
return MaterialContainerTransform().apply {
addTarget(R.id.details_poster)
duration = 450
pathMotion = MaterialArcMotion()
}
}
in 2nd fragment :
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
super.onCreateView(inflater, container, savedInstanceState)
ViewCompat.setTransitionName(binding.detailsPoster, tmdbItem.name)
}
Source code can be found here : https://github.com/alirezaeiii/TMDb-Paging
Do you know how can I avoid 1st fragment in 1st Activity go to background?

BottomSheetDialog dims background being used in ViewPager2

I have a fragment that I want to display as an embedded fragment in a ViewPager and as a Bottom Sheet. I followed this https://developer.android.com/reference/android/app/DialogFragment#DialogOrEmbed and created a DialogFragment
private val mViewModel: CardPricesViewModel by viewModels()
private val binding by viewBinding(FragmentCardDetailPricesBinding::inflate)
companion object {
// This is the same value as the navArg name so that the SavedStateHandle can acess from either
const val ARG_SKU_IDS = "skuIds"
fun newInstance(skus: List<Long>?) =
CardDetailPricesFragment().apply {
arguments = Bundle().apply {
putLongArray(ARG_SKU_IDS, skus?.toLongArray())
}
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
}
However, when it gets inflated in a ViewPager the background dims as though it is a BottomSheetDialogFragment
However, when I manually do it with
supportFragmentManager
.beginTransaction()
.replace(binding.cardPricesFragmentContainer.id, cardDetailPricesFragment)
.commit()
It works fine. I see that the FragmentStateAdapter uses FragmentViewHolders instead of the using transactions directly (?), so I am not sure how to resolve this issue. I see that onCreateDialog() is being called, so if I call dismiss() after onViewCreated(), it works properly, but I am not sure if this a workaround
After some digging, I found the DialogFragment.setShowsDialog(boolean) method that you can use to disable the dialog being created.

switch from one fragment to another in main activity

Example of what i'm trying to achieve https://i.stack.imgur.com/lhp10.png
I'm stuck on the part where i need to switch to createUserFragment from defaultFragment
i attached this to my button, but nothing happens when i press it, not sure what's wrong there
MainActivity
class MainActivity : AppCompatActivity() {
lateinit var appBarConfiguration: AppBarConfiguration
lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
navController = findNavController(R.id.hostFragment)
appBarConfiguration = AppBarConfiguration(navController.graph,drawer_layout)
NavigationUI.setupActionBarWithNavController(this, navController,drawer_layout)
NavigationUI.setupWithNavController(navigation_drawer,navController)
}
}
override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(navController,appBarConfiguration)
}
defaultFragment
class defaultFragment : Fragment() {
private lateinit var binding: defaultFragmentBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
binding = defaultFragmentBinding.inflate(inflater)
return (binding.root)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.createNewBlankFAB.setOnClickListener{
val transaction = (activity as MainActivity).supportFragmentManager.beginTransaction()
transaction.replace(R.id.defaultFragment, createUserFragment())
transaction.disallowAddToBackStack()
transaction.commit()
}
}
}
My questions are:
1)How can i fix my thing?
2)Will it work properly? I.e no memory leaks or some other funky stuff
3)Do i have to use another fragment for data input or maybe there's another way?
UPDATE
I stil have no idea how this works, but apparently i was replacing wrong fragment, i switched this R.id.defaultFragment in transaction.replace to R.id.hostFragment from which, i assume, all other fragments spawn, but now it just spawn on top of my existing fragment and the drawer button doesn't change its state, i guess i have to either do all of this differently or somehow pass to the drawer navigation information that current fragment was changed?
This is how we navigate within fragments in Navigation component library . We use navigate to then id of the destination Fragment which is defined in navgraph
binding.createNewBlankFAB.setOnClickListener{
Navigation.findNavController(view).navigate(R.id.createUserFragment);
}
I think you have to call "transaction.add" instead of "replace" since you are calling your fragment from an activity. Replace is called when you call a fragment from another fragment, I believe.

Categories

Resources