java.lang.IllegalStateException: Fragment already added: MovieFragment - android

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)
)

Related

How to pass data from adapter to fragment?

I've been trying to pass data(the email and phone of a user) from my adapter to my fragment. From what I've read online I should use a interface for this but I cant the data into my fragment still. Can anyone explain in steps how I should add a interface and how to put data into my interface from my adapter so I can call it in my fragment. Or is there another way to pass data from my adapter to my fragment. Below are my adapter and my fragment.
Adapter:
package ie.wit.savvytutor.adapters
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import ie.wit.savvytutor.R
import ie.wit.savvytutor.activity.MainActivity
import ie.wit.savvytutor.fragments.ViewChatFragment
import ie.wit.savvytutor.models.UserModel
class UserAdapter(private val userList: ArrayList<UserModel>, val context: Context) :
RecyclerView.Adapter<UserAdapter.UserViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val itemView =
LayoutInflater.from(parent.context).inflate(R.layout.user_layout, parent, false)
return UserViewHolder(itemView)
}
class UserViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val username: TextView = itemView.findViewById(R.id.userNameView)
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int, ) {
val currentItem = userList[position]
holder.username.text = currentItem.email
holder.itemView.setOnClickListener {
println(currentItem)
val optionsFrag = ViewChatFragment()
(context as MainActivity).getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, optionsFrag, "OptionsFragment").addToBackStack(
null
)
.commit()
}
}
override fun getItemCount(): Int {
return userList.size
}
}
Fragment
package ie.wit.savvytutor.fragments
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.Nullable
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.database.*
import ie.wit.savvytutor.R
import ie.wit.savvytutor.adapters.UserAdapter
import ie.wit.savvytutor.models.UserModel
class TutorChatFragment : Fragment() {
private lateinit var userRecyclerView: RecyclerView
private lateinit var userArrayList: ArrayList<UserModel>
private lateinit var dbRef: DatabaseReference
private lateinit var mAuth: FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
dbRef = FirebaseDatabase.getInstance("DATABASE LINK").getReference("Users").ref
mAuth = FirebaseAuth.getInstance()
}
#Nullable
override fun onCreateView(
inflater: LayoutInflater,
#Nullable container: ViewGroup?,
#Nullable savedInstanceState: Bundle?
): View {
//inflate the fragment layout
val root = inflater.inflate(R.layout.tutor_chat_fragment, container, false)
userRecyclerView = root.findViewById(R.id.userListView)
userRecyclerView.layoutManager = LinearLayoutManager(context)
userRecyclerView.setHasFixedSize(true)
userArrayList = arrayListOf<UserModel>()
getUser()
return root
}
private fun getUser() {
userArrayList.clear()
dbRef.addValueEventListener(object: ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
for (postSnapshot in snapshot.children) {
val currentUser = postSnapshot.getValue(UserModel::class.java)
//BUG FIX 1.26.13
val email = currentUser?.email
if (email != null) {
userArrayList.add(currentUser)
}
userRecyclerView.adapter?.notifyDataSetChanged()
userRecyclerView.adapter = context?.let { UserAdapter(userArrayList, it) }
}
}
override fun onCancelled(error: DatabaseError) {
TODO("Not yet implemented")
}
})
}
}
If you want to use an interface, you just need to define one with a function to receive your data, make the fragment implement it, then pass the fragment to the adapter as an implementation of that interface:
data class UserData(val email: String, val phone: String)
class UserAdapter(
private val userList: ArrayList<UserModel>,
val context: Context,
val handler: UserAdapter.Callbacks // added this here, so you're passing it in at construction
) : RecyclerView.Adapter<UserAdapter.UserViewHolder>() {
...
private fun doWhatever(email: String, phone: String) {
// pass the data to the handler (which will probably be your Fragment)
handler.handleUserData(UserData(email, phone))
}
// nested inside the UserAdapter class to keep things tidy
interface Callbacks {
fun handleUserData(data: UserData)
}
}
Then in the Fragment:
// add the Callbacks interface type
class TutorChatFragment : Fragment(), UserAdapter.Callbacks {
override fun onCreateView(
inflater: LayoutInflater,
#Nullable container: ViewGroup?,
#Nullable savedInstanceState: Bundle?
): View {
...
userRecyclerView.layoutManager = LinearLayoutManager(context)
// set up the adapter here, passing this fragment as the Callbacks handler
userRecyclerView.adapter = UserAdapter(userArrayList, context, this)
...
}
// interface implementation
override fun handleUserData(data: UserData) {
// whatever
}
}
And that's it. You're not hardcoding a dependency on that particular Fragment type, just the interface, and this fragment implements it so it can pass itself.
A more Kotliny way to do it is to ignore interfaces and just pass a function instead
class UserAdapter(
private val userList: ArrayList<UserModel>,
val context: Context,
val handler: (UserData) -> Unit // passing a function that takes a UserData instead
) : RecyclerView.Adapter<UserAdapter.UserViewHolder>() {
...
private fun doWhatever(email: String, phone: String) {
// call the handler function with your data (you can write handler.invoke() if you prefer)
handler(UserData(email, phone))
}
}
// no interface this time
class TutorChatFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
#Nullable container: ViewGroup?,
#Nullable savedInstanceState: Bundle?
): View {
...
userRecyclerView.layoutManager = LinearLayoutManager(context)
// pass in a handler function
userRecyclerView.adapter = UserAdapter(userArrayList, context) { userData ->
handleUserData(userData)
}
// or if you're just passing it to that function down there,
// you could do UserAdapter(userArrayList, context, ::handleUserData)
// and pass the function reference
...
}
// might be convenient to still do this in its own function
private fun handleUserData(data: UserData) {
// whatever
}
}
Ideally you should be doing what I've done there - create the adapter once during setup, and have a function on it that allows you to update it. Your code creates a new one each time you get data. You do this the same way in both though
Your other option is using a view model that the adapter and fragment both have access to, but this is how you do the interface/callback approach
Actually there is one very easy way to get data from your adapter in to your fragment or activity. It is called using Higher Order Functions.
In your adapter
Add higher order function in your adapter.
class UserAdapter(private val userList: ArrayList<UserModel>, val context: Context) :
RecyclerView.Adapter<UserAdapter.UserViewHolder>() {
//your rest of the adapter's code
private var onItemClickListener:((UserModel)->Unit)? = null
fun setOnItemClickListener(listener: (UserModel)->Unit) {
onItemClickListener = listener
}
}
In Your UserViewHolder
val rootView = itemView.rootView
In Your onBindViewHolder
set a click listener on rootView
holder.rootView.setOnClickListener {
onItemClickListener?.let{
it(currentItem)
}
}
In Your Fragment
//create the instance of UserAdapter
userAdapter.setOnItemClickListener {
//here you have your UserModel in your fragment, do whatever you want to with it
}
And, a suggestion in the last. Start using ViewBinding, it will save you from a lots of hectic work.

Viewpager2, tablayout doesn't work with lazyloader

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()
}

Android Kotlin - Navigate between fragments using textView in recyclerView

I would like to navigate between fragments using a textView inside a recyclerview.
Currently I am successfully navigating between fragments using the item, but I would like to go further and navigate using the individual textViews within an item.
The recyclerview is inflated from a local room database.
Below is my adapter
package com.benb.inventory
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Toast
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.benb.inventory.data.Item
import com.benb.inventory.data.getFormattedPrice
import com.benb.inventory.databinding.ItemListItemBinding
class ItemListAdapter(private val onItemClicked: (Item) -> Unit) :
ListAdapter<Item, ItemListAdapter.ItemViewHolder>(DiffCallback) {
class ItemViewHolder(internal var binding: ItemListItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: Item) {
binding.apply {
itemName.text = item.itemName
itemPrice.text = item.getFormattedPrice()
itemShop.text = item.shop.toString()
itemShop.setOnClickListener{
val shopName = itemShop.text.toString()
Toast.makeText(root.context, "Clicked: ${item.shop}", Toast.LENGTH_SHORT ).show()
}
}
}
}
companion object {
private val DiffCallback = object : DiffUtil.ItemCallback<Item>() {
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem.itemName == newItem.itemName
}
}
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): ItemListAdapter.ItemViewHolder {
return ItemViewHolder(ItemListItemBinding.inflate(LayoutInflater.from(parent.context)))
}
override fun onBindViewHolder(holder: ItemListAdapter.ItemViewHolder, position: Int) {
val current = getItem(position)
holder.itemView.setOnClickListener {
onItemClicked(current)
}
holder.binding.itemShop.setOnClickListener {
onItemClicked(current)
val shopName = current.shop
fun showShopList(shopName: String) {
val shopName = holder.binding.itemShop.text.toString()
val action = ItemListFragmentDirections.actionItemListFragmentToShopItemFragment(shopName)
}
}
holder.bind(current)
}
}
This is the fragment that contains the list
package com.benb.inventory
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.benb.inventory.databinding.ItemListFragmentBinding
import kotlinx.coroutines.InternalCoroutinesApi
#InternalCoroutinesApi
class ItemListFragment : Fragment() {
#InternalCoroutinesApi
private val viewModel: InventoryViewModel by activityViewModels {
InventoryViewModelFactory(
(activity?.application as InventoryApplication).database.itemDao()
)
}
private var _binding: ItemListFragmentBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = ItemListFragmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val adapter = ItemListAdapter {
val action = ItemListFragmentDirections.actionItemListFragmentToItemDetailFragment(it.id)
this.findNavController().navigate(action)
}
binding.recyclerView.adapter = adapter
viewModel.allItems.observe(this.viewLifecycleOwner) {items ->
items.let {
adapter.submitList(it)
}
}
binding.itemShop.setOnClickListener{
val shop = binding.itemShop.text.toString()
val action = ItemListFragmentDirections.actionItemListFragmentToShopItemFragment(shop)
viewModel.retrieveShopItems(shop)
this.findNavController().navigate(action)
}
binding.recyclerView.layoutManager = LinearLayoutManager(this.context)
binding.floatingActionButton.setOnClickListener {
val action = ItemListFragmentDirections.actionItemListFragmentToAddItemFragment(
getString(R.string.add_fragment_title)
)
this.findNavController().navigate(action)
}
}
}
For clarity this is what the inflated recyclerview looks like, it can contain many items, I have only added one.
At the moment if I click anywhere on the item it takes you to a screen with more detail about the fragment.
[A screenshot of the recycler view][1]
I would like to be able to navigate to a specific fragment depending on the textView clicked.
For example one fragment that only contains other products from the same shop.
As you may notice, I have been able to add an onClickListener in the ViewHolder class, it creates a Toast.
I have not had success in using the same onClickListener to navigate.
Thank you very much.
P.S. Any other advice is welcome, particularly if anyone knows what the #internalcoroutinesAPI thing is about please tell me!
[1]: https://i.stack.imgur.com/k6Td3.png

Creating custom class for YoutubePlayerSupportFragment (Kotlin) Android Studio 4.0

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:

Null Pointer within Fragment using Synthetic

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)
}
}
}

Categories

Resources