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()
Related
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.
I have tried this problem all days long and still cannot be resolved.
I am writing an app which has three fragments, connected by Navigation Component (with only one activity).
Fragment A
Fragment B
Fragment C
And I observed one LiveData variable from ViewModel in these three fragments.
But only Fragment B is responding. The other two are not.
I have read almost all similar problems from this stack over flow. But still can't see the answer.
Please suggest me.
I have tried removing the observer from FragmentB (which is ok with observing Live Data). But the other two is not still observing)
Here is my code:
Fragment B: (Observe Working)
class ServerFragment : Fragment() {
private lateinit var binding: ServerLayoutBinding
private val vm: DataModel by viewModels()
private lateinit var devices_observer: Observer<MutableList<ClientSocket>>
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = ServerLayoutBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
var client_list_adapter = ClientListAdapter(layoutInflater)
binding.clientListRcv.apply {
layoutManager = LinearLayoutManager(this.context)
addItemDecoration(
DividerItemDecoration(
this.context,
DividerItemDecoration.VERTICAL
)
)
adapter = client_list_adapter
}
client_list_adapter.submitList(vm.clientDevices.value?.toList())
vm.clientDevices.observe(viewLifecycleOwner, Observer {
client_list_adapter.submitList(vm.clientDevices.value?.toList())} )
}
}
Fragment C: (Observe Not Working)
class FileListFragment: Fragment() {
private val vm: DataModel by viewModels()
private lateinit var binding: FileListBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding=FileListBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//Here I am populating my recycler view with ViewModel data
//And ViewModel live dat is obeserving here and
vm.clientDevices.observe(viewLifecycleOwner, Observer{
///Here UI updating work will be done. But did'not responded. Here is the problem point I think.
Toast.makeText(context, "OKKK", Toast.LENGTH_SHORT).show()
})
}
}
My Goal
I am trying to access the widget that was created inside my fragment using viewBinding.
What I have done / Info about my app
The language I am using is kotlin.
I have already added the code below into gradle:
buildFeatures{
dataBinding = true
viewBinding = true
}
I have tested binding.aTextView.setText("Code working.") inside my main activity and it works.
What's the problem
I have tested the setText code inside activity and it works. The problem right now is the same code when I move into the fragment it wouldn't work. And I am sure that the code has been executed as I putted a toast above it and the toast executed successfully which mean it should have at least reached that point before but not sure due to what reason there wasn't any changes.
My mainActivity Code:
class MainProgramActivity : AppCompatActivity() {
lateinit var binding: ActivityMainProgramBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainProgramBinding.inflate(layoutInflater)
setContentView(binding.root)
replaceFragment(FragmentMainPage())
}
private fun replaceFragment(fragment: Fragment){
val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.fragmentContainerView,fragment)
fragmentTransaction.commit()
}
}
My fragment code:
class FragmentMainPage : Fragment(R.layout.fragment_main_page) {
lateinit var binding: FragmentMainPageBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
Toast.makeText(getActivity(),"Text!",Toast.LENGTH_SHORT).show();
binding = FragmentMainPageBinding.inflate(layoutInflater)
binding.aTextView.setText("Code working") //<-- I want this code to make changes towards the textView
return super.onCreateView(inflater, container, savedInstanceState)
}
}
The aTextView itself is empty at the beginning, the expected result will be the aTextView to show "Code working".
I see two problems with your code. First, exactly what Michael pointed out. You're returning the super method when you should be returning the View you just created (binding.root). Second, you're currenly leaking your fragment. When you viewbind a fragment, you are supposed to set the variable to null in onDestroyView(), as per defined in the documentation.
class FragmentMainPage : Fragment(R.layout.fragment_main_page) {
private var _binding: FragmentMainPageBinding? = null
private val binding get() = _binding!! // non-null variable in order to avoid having safe calls everywhere
// create the view through binding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentMainPageBinding.inflate(layoutInflater, container, false)
return binding.root
}
// view already created, do whatever with it
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.aTextView.setText("Code working")
}
// clear the binding in order to avoid memory leaks
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
I'm trying to implement a BaseFragment in which I will pass the layout resource on it and it should outputs the binding to work in the fragment itself instead of need to do it everytime the fragment is extended.
For example I have this BaseFragment
open class BaseFragment(#LayoutRes contentLayoutId : Int = 0) : Fragment(contentLayoutId) {
private lateinit var onInteractionListener: OnFragmentInteractionListener
val toolbar : Toolbar?
get() {
return if(activity is BaseActivity)
(activity as BaseActivity).toolbar
else
null
}
override fun onAttach(context: Context) {
super.onAttach(context)
setOnInteractionListener(context)
}
...
In which I use like this
class A(): BaseFragment(R.layout.myFragment) { ... }
Now, if I use this I will need to do the definition of the binding class again in my onCreateView
class A(): BaseFragment(R.layout.myFragment) {
private lateinit var binding: MyFragmentBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.myFragment, container, false)
return binding.root
}
override fun onDestroy(){
binding = null
}
}
What I want to implement is that since I'm passwing the layout to my BaseFragment, I want my BaseFragment to handle the creation of the binding and just return me the binding in the fragment which I use to extend BaseFragment
What I want to have is something like this
class A(): BaseFragment(R.layout.myFragment) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.myTextView = ""
}
}
So my question is, how I can implement inside BaseFragment the onDestroy() and the onCreateView to always create a binding for me from the layout I'm passing in ?
I heard that I should use reflection but I'm not that sure on how to accomplish it
I didn't hear about the possibility to get the databinding just from a layout, but even if it's possible, I don't think that is the recommended way, because of two reasons:
Reflection is slow
It makes things more complicated than they are.
Instead of making magic with Reflection, you could do something like this:
abstract class BaseFragment<out VB: ViewDataBinding>(
private val layout: Int,
// Other Dependencies if wanted
) : Fragment() {
abstract val viewModel: ViewModel
// other variables that all fragments need
// This does not cause any memory leak, because you are not storing the binding property.
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = DataBindingUtil.inflate<VB>(inflater, layout, container, false).apply {
lifecycleOwner = viewLifecycleOwner
setVariable(BR.viewModel, viewModel)
}.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// Do some basic work here that all fragments need
// like a progressbar that all fragments share, or a button, toolbar etc.
}
And then, when you still need the bindingProperty, I would suggest the following library (it handles all the onDestoryView stuff etc):
implementation 'com.kirich1409.viewbindingpropertydelegate:viewbindingpropertydelegate:1.2.2'
You can then use this like:
class YourFragment(yourLayout: Int) : BaseFragment<YourBindingClass>() {
private val yourBinding: YourBindingClass by viewBinding()
override val viewModel: YourViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// do binding stuff
}
}
Let me know if this worked for you.
Cheers
I'm using viewmodel with injection. In my scnario, I have two fragments. A one is A, and the other is B.
When using A Viewmodel, it's working. But after changing A -> B -> A, The A Viewmodel is not working.
I don't understand why the viewmodel is not working. the only way I solved is generating a new fragment A. But I want to use DaggerFragment. please let me know why this happens.
viewmodel injection code
class AFragment : DaggerFragment(){
#Inject
lateinit var viewModelFactory : ViewModelProvider.Factory
private val viewModel by viewModels<HomeViewModel>{viewModelFactory}
override fun onCreateView (inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentHomeFirstwalkingBinding.inflate(layoutInflater, container,false).apply {
viewmodel = viewModel
}
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initLayout()
}
private fun initLayout(){
binding.title.setOnClickListener {
viewModel.getTrainingDataList("sad")
}
}
}
binding.title.setOnClickListener is not working after changing fragments.