I am getting null pointers (sometimes) on views within fragments using synthetic.
I do not know what is wrong because I am declaring the fragments in XML and I think that when I call the populate method from the Activity, the root view is already created
Anyway, I do not believe is correct check this each time I call the populate...
I write an example code of what happens (the null pointer would be on tv):
The fragment:
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.fragment_foo.*
class FooFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_foo, container, false)
}
fun populate(fooName: String) {
tv.text = fooName
}
}
And the XML related within the XML of the Activity related
<fragment
android:id="#+id/fFoo"
android:name="com.foo.FooFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:layout="#layout/fragment_foo"/>
This would the Activity related:
class FooActivity : AppCompatActivity() {
private lateinit var fooFragment: FooFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_foo)
fooFragment = fFoo as FooFragment
}
private fun loadDataProductionInfo(productionId: Long) {
FooAPIParser().getFoo {
initFoo(it.fooName)
}
}
private fun initFoo(fooName: String) {
fooFragment.populate(fooName)
}
}
And finally the XML of the specific fragment:
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/tvFoo"
style="#style/Tv"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Kotlin synthetic properties are not magic and work in a very simple way. When you access btn_K, it calls for getView().findViewById(R.id.tv)
The problem is that you are accessing it too soon getView() returns null in onCreateView. Try doing it in the onViewCreated method:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
//Here
}
}
Using views outside onViewCreated.
tv?.text = "kotlin safe call"
If you are going to use Fragments you need to extend FragmentActivity
Thinking in your answers I have implemented a patch. I do not like very much, but I think it should work better than now. What do you think?
I would extend each Fragment I want to check this from this class:
import android.os.Bundle
import android.os.Handler
import android.view.View
import androidx.fragment.app.Fragment
open class BaseFooFragment : Fragment() {
private var viewCreated = false
private var attempt = 0
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewCreated = true
}
fun checkViewCreated(success: () -> Unit) {
if (viewCreated) {
success()
} else {
initLoop(success)
}
}
private fun initLoop(success: () -> Unit) {
attempt++
Handler().postDelayed({
if (viewCreated) {
success()
} else {
if (attempt > 3) {
return#postDelayed
}
checkViewCreated(success)
}
}, 500)
}
}
The call within the Fragment would be more or less clean:
fun populate(fooName: String) {
checkViewCreated {
tv.text = fooName
}
}
Finally, I have received help to find out a better answer.
open class BaseFooFragment : Fragment() {
private var listener: VoidListener? = null
private var viewCreated = false
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewCreated = true
listener?.let {
it.invoke()
listener = null
}
}
override fun onDestroyView() {
viewCreated = false
super.onDestroyView()
}
fun doWhenViewCreated(success: VoidListener) {
if (viewCreated) {
success()
} else {
listener = success
}
}
}
The VoidListener is simply this:
typealias VoidListener = () -> Unit
One way to do this more generic (for example, when you want to use more than one listener) could be like this:
open class BaseFooFragment : Fragment() {
private val listeners: MutableList<VoidListener> = mutableListOf()
private var viewCreated = false
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewCreated = true
listeners.forEach { it() }
listeners.clear()
}
override fun onDestroyView() {
viewCreated = false
super.onDestroyView()
}
fun doWhenViewCreated(success: VoidListener) {
if (viewCreated) {
success()
} else {
listeners.add(success)
}
}
}
Related
I have ViewPager2, Tablayout with Lazyloder.
At the first getting in the fragment it works, dots move while loading but after I swipe from one fragment to another and back to my first fragment dots don't move anymore.
.xml
<com.agrawalsuneet.dotsloader.loaders.LazyLoader
android:id="#+id/dots_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_margin="20dp"
android:layout_marginTop="30dp"
app:lazyloader_animDur="400"
app:lazyloader_dotsDist="10dp"
app:lazyloader_dotsRadius="7dp"
app:lazyloader_firstDelayDur="150"
app:lazyloader_firstDotColor="#fff"
app:lazyloader_interpolator="#android:anim/decelerate_interpolator"
app:lazyloader_secondDelayDur="300"
app:lazyloader_secondDotColor="#fff"
app:lazyloader_thirdDotColor="#fff"
tools:ignore="MissingClass" />
fragment
#AndroidEntryPoint
class InspirationQuotesFragment :
BaseFragment<FragmentInspirationQuotesBinding, InspirationQuotesViewModel>
(FragmentInspirationQuotesBinding::inflate) {
override val vm: InspirationQuotesViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
backgroundAnimation()
getRandomPicture()
onFloatingActionClick()
}
override fun onResume() {
super.onResume()
}
private fun getRandomPicture() {
layout.floatingActionBtn.setOnClickListener {
onFloatingActionClick()
}
}
private fun onFloatingActionClick() {
layout.floatingActionBtn.animate().apply {
rotationBy(360f)
duration = 1000
}.start()
layout.ivRandomPicture.visibility = View.GONE
makeApiRequest()
}
private fun backgroundAnimation() {
val animationDrawable: AnimationDrawable = layout.rlLayout.background as AnimationDrawable
animationDrawable.apply {
setEnterFadeDuration(1000)
setExitFadeDuration(3600)
start()
}
}
private fun makeApiRequest() = lifecycleScope.launch {
vm.randomPicture
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.collect { response ->
if (response.fileSizeBytes < 600000) {
Log.d("fragment", "itGetsValue")
Glide.with(requireContext()).load(response.url)
.into(layout.ivRandomPicture)
layout.ivRandomPicture.visibility = View.VISIBLE
} else {
onFloatingActionClick()
}
}
}
}
viewpagerfragment
package com.example.learningmanager.fragments
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.View
import androidx.fragment.app.viewModels
import com.example.learningmanager.base.ui.BaseFragment
import com.example.learningmanager.databinding.FragmentViewPagerBinding
import com.example.learningmanager.fragments.inspirationquotes.ui.InspirationQuotesFragment
import com.example.learningmanager.fragments.notesmanager.ui.NotesManagerFragment
import com.example.learningmanager.fragments.setgoals.ui.SetGoalsFragment
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
#AndroidEntryPoint
class ViewPagerFragment #Inject constructor() : BaseFragment<FragmentViewPagerBinding, ViewPagerViewModel>(
FragmentViewPagerBinding::inflate
) {
override val vm: ViewPagerViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// viewpager with list of fragments
val fragmentList = arrayListOf<Fragment>(
InspirationQuotesFragment(),
NotesManagerFragment(),
SetGoalsFragment()
)
val adapter = ViewPagerAdapter(
fragmentList,
childFragmentManager,
lifecycle
)
layout.viewPager.adapter = adapter
// initialize tablayout with names
TabLayoutMediator(layout.tabLayout, layout.viewPager) {tab, position ->
when (position) {
0 -> tab.text = "Inspiration"
1 -> tab.text = "Notes"
2 -> tab.text = "Set Goals"
}
}.attach()
listener if tab changes and it is possible to make specific action
layout.tabLayout.addOnTabSelectedListener(object: TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
}
override fun onTabReselected(tab: TabLayout.Tab?) {
}
})
}
}
viewpageradapter
class ViewPagerAdapter(list: ArrayList<Fragment>, fm: FragmentManager, lifecycle: Lifecycle) :
FragmentStateAdapter(fm, lifecycle) {
val fragmentList = list
override fun getItemCount(): Int {
return fragmentList.size
}
override fun createFragment(position: Int): Fragment {
return fragmentList[position]
}
}
Have you got an information how to fix it or maybe change to another library?
Thank you
I ran the same sample at my end. I found out that the trick is to use initView() on onResume of a fragment. Which internally calls startLoading.
Sample code:
lateinit var dotsLoader: LazyLoader
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_test1, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
dotsLoader = view.findViewById(R.id.dots_loading)
}
override fun onResume() {
super.onResume()
dotsLoader.initView()
}
I have two interfaces ...
interface Calculator { fun getResult(input: String?): Result? }
interface Result { val asString: String }
i am able to initialize Calculator interface, but when i am trying to Callback, i get an error message ...for looping Stackoverflow error message..
Any idea why? how can i solve this problem?
I know the problem is in this line
***override fun getResult(input:String?):Result=getResult(textInputEditText.text.toString()) ***-
but i dont know how to fix it, any help is really welcome! thanks in advance
for your time and help!!
class ComputeFragment #ContentView constructor() : Fragment(R.layout.fragment_task) {
private var taskBinding: FragmentTaskBinding? = null
private val calculator: Calculator? = null
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is Calculator) {
calculator = context
} else {
throw RuntimeException(context.toString() + " must implement
Calculator interface")
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState:Bundle?): View {
val binding = FragmentTaskBinding.inflate(layoutInflater)
taskBinding = binding
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
taskBinding
?.buttonFirst
?.setOnClickListener {
NavHostFragment.findNavController(this#ComputeFragment).navigate(R.id.action_FirstFragment_to_SecondFragment)
}
taskBinding
?.buttonCompute
?.setOnClickListener { v: View ->
if (calculator == null) {
Snackbar.make(v, R.string.please_implement, Snackbar.LENGTH_LONG).show()
return#setOnClickListener
}else {
val result = calculator.getResult(taskBinding?.textInputEditText?.text.toString())
taskBinding?.result?.text = result?.asString
}}}}
class TaskActivity : appCompatActivity() , Calculator {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_task)
setSupportActionBar(toolbar}
}
override fun getResult(input: String?): Result? =
getResult(textInputEditText.text.toString())
}}```
I am using StateFlow in my app and in my Fragment I use this to -
private var job: Job? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
job = lifecycleScope.launchWhenResumed {
viewModel.getData().collect {
// ...
}
}
}
override fun onPause() {
job?.cancel()
super.onPause()
}
As you see I cancel the job in onPause. How could I use a generalized function so that I can avoid doing the job?.cancel in every fragment.
I prefer not to use a BaseFragment
A simple solution would be to utilize the fragments lifecycle to automatically cancel the job when it is paused.
fun CoroutineScope.launchUntilPaused(lifecycleOwner: LifecycleOwner, block: suspend CoroutineScope.() -> Unit){
val job = launch(block = block)
lifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onPause(owner: LifecycleOwner) {
job.cancel()
lifecycleOwner.lifecycle.removeObserver(this)
}
})
}
//Usage
class MyFragment: Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
lifecycleScope.launchUntilPaused(this){
someFlow.collect{
...
}
}
}
}
If you have many of these jobs per fragment, I would advice to use a custom CoroutineScope instead, to avoid having many lifecycle observers active.
class CancelOnPauseScope(lifecycleOwner: LifecycleOwner): CoroutineScope by MainScope(){
init{
lifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver{
override fun onPause(owner: LifecycleOwner) {
cancel()
lifecycleOwner.lifecycle.removeObserver(this)
}
})
}
}
class MyFragment: Fragment() {
private val scope = CancelOnPauseScope(this)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
scope.launch{
someFlow.collect{
...
}
}
}
}
a new way:
// Start a coroutine in the lifecycle scope
lifecycleScope.launch {
// repeatOnLifecycle launches the block in a new coroutine every time the
// lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
repeatOnLifecycle(Lifecycle.State.STARTED) {
}
}
public class AnyOfYourFragments extends AbsractFragment{
//you do here what you want to do
}
And in your AbstractFragment:
public abstract class AbstractFragment extends Fragment{
private var job: Job? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
job = lifecycleScope.launchWhenResumed {
viewModel.getData().collect {
// ...
}
}
}
override fun onPause() {
job?.cancel()
super.onPause()
}
}
I don't know kotlin, so my code is kind of mix with java but i'm sure you got the idea
In my app I have a MainActivity which has mobile navigation implemented with a navBar and all that stuff. When I navigate to a fragment there needs to be a Youtube Video Player inside. As I'm developing a one activity application so far I tried to implement the Fragment approach on the Youtube API.
I'm having issues with YoutubePlayerSupportFragment. I made it work following this suggestion: https://stackoverflow.com/a/58792809/13150066
But this solution is, to me, kind of shady. I'm afraid this solution will crash sometime, or will not work as the API itself would.
This is the error was having with 'android.support.v4.app.Fragment'
And as the suggestion above suggests... I created a new custom class, YoutubePlayerSupportFragmentX which extends from the Fragment class that I have no issues with, androidx.fragment.app.Fragment, and this is it's code:
YoutubePlayerSupportFragmentX.kt
package com.google.android.youtube.player //<--- IMPORTANT!!!!
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.google.android.youtube.player.internal.ab
import java.util.*
class YouTubePlayerSupportFragmentX : Fragment(), YouTubePlayer.Provider {
private val a = ViewBundle()
private var b: Bundle? = null
private var c: YouTubePlayerView? = null
private var d: String? = null
private var e: YouTubePlayer.OnInitializedListener? = null
override fun initialize(var1: String, var2: YouTubePlayer.OnInitializedListener) {
d = ab.a(var1, "Developer key cannot be null or empty")
e = var2
a()
}
private fun a() {
if (c != null && e != null) {
c?.a(this.activity, this, d, e, b)
b = null
e = null
}
}
override fun onCreate(var1: Bundle?) {
super.onCreate(var1)
b = var1?.getBundle("YouTubePlayerSupportFragment.KEY_PLAYER_VIEW_STATE")
}
override fun onCreateView(var1: LayoutInflater, var2: ViewGroup?, var3: Bundle?): android.view.View? {
c = YouTubePlayerView(Objects.requireNonNull(this.activity), null, 0, a) // and this line compiles but gives red warning
a()
return c
}
override fun onStart() {
super.onStart()
c?.a()
}
override fun onResume() {
super.onResume()
c?.b()
}
override fun onPause() {
c?.c()
super.onPause()
}
override fun onSaveInstanceState(var1: Bundle) {
super.onSaveInstanceState(var1)
(if (c != null) c?.e() else b)?.let { var2 ->
var1.putBundle("YouTubePlayerSupportFragment.KEY_PLAYER_VIEW_STATE", var2)
}
}
override fun onStop() {
c?.d()
super.onStop()
}
override fun onDestroyView() {
this.activity?.let { c?.c(it.isFinishing) }
c = null
super.onDestroyView()
}
override fun onDestroy() {
if (c != null) {
val var1 = this.activity
c?.b(var1 == null || var1.isFinishing)
}
super.onDestroy()
}
private inner class ViewBundle : YouTubePlayerView.b {
override fun a(var1: YouTubePlayerView, var2: String, var3: YouTubePlayer.OnInitializedListener) {
e?.let { initialize(var2, it) }
}
override fun a(var1: YouTubePlayerView) {}
}
companion object {
fun newInstance(): YouTubePlayerSupportFragmentX {
return YouTubePlayerSupportFragmentX()
}
}
}
And this is my fragment class in which I implement the YoutubePlayerSupportFragmentX
VideoPlayerFragment.kt
package com.vegdev.vegacademy.ui.learning
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.google.android.youtube.player.*
import com.vegdev.vegacademy.R
class VideoPlayerFragment : Fragment() {
private var link: String? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
arguments?.let {
val safeArgs = VideoPlayerFragmentArgs.fromBundle(it)
link = safeArgs.link
}
val youtubePlayerFragment = YouTubePlayerSupportFragmentX.newInstance()
val transaction = childFragmentManager.beginTransaction()
transaction.replace(R.id.player, youtubePlayerFragment).commit()
youtubePlayerFragment.initialize(resources.getString(R.string.API_KEY), object : YouTubePlayer.OnInitializedListener {
override fun onInitializationSuccess(
p0: YouTubePlayer.Provider?,
p1: YouTubePlayer?,
p2: Boolean
) {
p1?.loadVideo(link)
}
override fun onInitializationFailure(
p0: YouTubePlayer.Provider?,
p1: YouTubeInitializationResult?
) {
}
})
return inflater.inflate(R.layout.fragment_video_player, container, false)
}
}
fragment_video_player.XML
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/blackBackground"
tools:context=".ui.learning.VideoPlayerFragment">
<FrameLayout
android:id="#+id/player"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
I tried changing dependencies, I tried erasing folder "/.idea/libraries", cleans and builds, everything I could find online. The only thing that did it was the suggestion above.
So my questions are:
Why am I getting that error with Fragment v4?
Am I implementing it wrong? Because it works just fine, except for the fullscreen but I've read that it's a common issue.
If you've implemented a Youtube Video inside a fragment, did you use another API? Is this the only one?
Put your class on a fragment directly over an activity such as:
<fragment android:name="com.google.android.youtube.player.YouTubePlayerSupportFragmentX" android:id="#+id/fragPlayer" android:layout_width="match_parent" android:layout_height="match_parent"/>
Your activity may implements YouTubePlayer.OnInitializedListener, and on your onCreate event call to object and initializate it:
val playerView : YouTubePlayerSupportFragmentX = supportFragmentManager.findFragmentById(R.id.fragPlayer) as YouTubePlayerSupportFragmentX
playerView.initialize(getString(R.string.YOUTUBE_API_KEY), this)
Remember to include your class YouTubePlayerSupportFragmentX on the com.google.android.youtube.Player.YouTubePlayerSupportFragmentX package.
In my case, I used only one API. This how it looks like:
got error fragment already added on my MyPagerAdapter class which extends FragmentPagerAdapter
this is my error logs
E/AndroidRuntime: FATAL EXCEPTION: main
Process: id.cahyowhy.tmdbmovieimplementation, PID: 8228
java.lang.IllegalStateException: Fragment already added: MovieFragment{df79747} (01cf33b8-9eb3-4dce-9519-384ca7dd7570) id=0x7f08014e android:switcher:2131231054:0}
at androidx.fragment.app.FragmentStore.addFragment(FragmentStore.java:67)
at androidx.fragment.app.FragmentManager.addFragment(FragmentManager.java:1563)
at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:405)
at androidx.fragment.app.FragmentManager.executeOps(FragmentManager.java:2167)
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1990)
at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1945)
at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1847)
at androidx.fragment.app.FragmentManager$4.run(FragmentManager.java:413)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
this is my pageradapter code
class MyPagerAdapter(fm: FragmentManager) :
FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
private val pages = listOf(
MovieFragment.newInstance(MovieListType.POPULAR),
MovieFragment.newInstance(MovieListType.TOP_RATED),
MovieFragment.newInstance(MovieListType.UPCOMING)
)
override fun getItem(position: Int): Fragment {
return pages[position]
}
override fun getCount(): Int {
return pages.size
}
override fun getPageTitle(position: Int): CharSequence? {
return when (position) {
0 -> "Popular"
1 -> "Top Rated"
else -> "Up Coming"
}
}
}
this is my movie fragment code
package id.cahyowhy.tmdbmovieimplementation.ui.activity.fragment
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import id.cahyowhy.tmdbmovieimplementation.databinding.MoviesFragmentResultBinding
import id.cahyowhy.tmdbmovieimplementation.ui.adapter.ItemTypeFactoryImpl
import id.cahyowhy.tmdbmovieimplementation.ui.adapter.VisitableRecyclerAdapter
import id.cahyowhy.tmdbmovieimplementation.ui.adapter.viewholder.ErrorStateItem
import id.cahyowhy.tmdbmovieimplementation.ui.adapter.viewholder.MovieItem
import id.cahyowhy.tmdbmovieimplementation.ui.base.BaseFragment
import id.cahyowhy.tmdbmovieimplementation.ui.base.BaseViewItem
import id.cahyowhy.tmdbmovieimplementation.ui.util.ext.observe
import org.koin.android.viewmodel.ext.android.viewModel
open class MovieFragment(
private val movieListType: MovieListType
) : BaseFragment() {
private val viewModel by viewModel<MovieFragmentViewModel>()
private lateinit var binding: MoviesFragmentResultBinding
private val movieAdapter by lazy {
VisitableRecyclerAdapter(
ItemTypeFactoryImpl(),
::onItemClicked
)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = MoviesFragmentResultBinding.inflate(
inflater,
container,
false
)
return binding.root
}
override fun onResume() {
super.onResume()
viewModel.loadData(0, movieListType)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
with(binding) {
recyclerView.adapter = movieAdapter
recyclerView.setHasFixedSize(true)
swipeRefresh.setOnRefreshListener { viewModel.loadData(0, movieListType) }
}
super.onViewCreated(view, savedInstanceState)
}
override fun observeChange() {
observe(viewModel.loading, ::handleLoading)
observe(viewModel.movies, ::onDataLoaded)
observe(viewModel.toastMessage, ::showSnackbarMessage)
}
private fun handleLoading(status: Boolean) {
binding.swipeRefresh.isRefreshing = status
}
private fun onDataLoaded(items: List<BaseViewItem>) {
movieAdapter.submitList(items)
}
private fun onItemClicked(viewItem: BaseViewItem, view: View) {
when (viewItem) {
is MovieItem -> {
Log.d("MovieItem", "MovieItem Click: ${viewItem.title}")
}
is ErrorStateItem -> {
viewModel.loadData(0, movieListType)
}
}
}
companion object {
fun newInstance(movieListType: MovieListType): MovieFragment =
MovieFragment(movieListType)
}
}
and this is when i called from MainActivity.kt
class MainActivity : BaseActivity() {
private lateinit var binding: ActivityMainBinding;
....
fun initView() {
with(binding) {
viewPager.adapter = MyPagerAdapter(supportFragmentManager)
viewPagerTab.setupWithViewPager(viewPager)
}
}
can anyone solve this.. thanks..
The problem is MyFragment is a class and the FragmentManager is something that you get by calling supportFragmentManager. This is provided to you by the system. For the fragment manager to be able to add new fragments to the back stack, the Fragments provided to it should be unique.
Now I do not know what your use case is but calling MyFragment.newInstance() does not provide you unique classes as they are just new instances of the same MyFragment class. The error log is stating that.
To get around this you can create three different Fragment classes like PopularMoviesFragment, TopRatedMoviesFragment and UpcomingMoviesFragment and then add them to your list on the adapter class.
I know it seems like a lot of work but this way you have three different fragments that shows data for three different cases of Movies and you can have additional logic in them. And you will also not hit java.lang.IllegalStateException: Fragment already added: MovieFragment error
Your Adapter code where you call:
private val pages = listOf(
MovieFragment.newInstance(MovieListType.POPULAR),
MovieFragment.newInstance(MovieListType.TOP_RATED),
MovieFragment.newInstance(MovieListType.UPCOMING)
)
should now look like:
private val pages = listOf(
PopularMoviesFragment.newInstance(MovieListType.POPULAR),
TopRatedMoviesFragment.newInstance(MovieListType.TOP_RATED),
UpcomingMoviesFragment.newInstance(MovieListType.UPCOMING)
)