how to remove the pager fragment from activity to prevent the old fragment instance to be restored by os - kotlin

It is an activity hosting a pager fragment which has android.support.v4.view.ViewPager and the adapter class derived from FragmentStatePagerAdapter.
The problem is from the pager fragment it has two or three fragments cached, but the data is not parseable (including a view dynamically getting from a 3rd part sdk), when sometime os recreate this fragment in case like minimize/reopen it, or after rotation the restored fragment are blank (using the one from cache and lacking data).
Not find a way to re-populate the fragments restored by os through the pager fragment's recreation flow.
Even tried in the pager fragment to clear the adapter's data in onActivityCreated()
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val childFragmentManager = childFragmentManager
mAdapter = ContentPagerAdapter( activity as Context,
childFragmentManager, emptyList())
mContentViewPager!!.setAdapter(mAdapter)
mAdapter?.setData(emptyList())
}
the adapter:
fun setData(itemsList: List<Data>) {
this.data = itemsList
notifyDataSetChanged()
}
the viewPager seems still showing the previous cachhed fragment without expected data after the re-creation flow is complete. (tried to make sure it is from the cached one, by in the fragment's onsaveInstance() to save the position of the data in the adapter data list, and the re-created fragment in the viewPager got that position, so it is a os re-created one from cache. But how could it be after at beging already set the adapter with empty list at onActivityCreated()).
Cannt resolve with clean the fragments by setting data to empty.
So tried to remove the page fragment from the activity at onDestroy() with hope that there will be no fragment to be put in cache:
class ContentPagerFragment :Fragment {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.content_pager, container, false)
mContentViewPager = view.findViewById(R.id.contentViewPager)
return view
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val childFragmentManager = childFragmentManager
mAdapter = ContentPagerAdapter( activity as Context,
childFragmentManager, emptyList())
mContentViewPager!!.setAdapter(mAdapter)
mAdapter?.setData(emptyList())
}
interface RemoveFragmentAtClose {
fun onContentPagerFragmentDoestroy()
}
override fun onDestroy() {
(activity as? RemoveAtCloseFragment)?
.onContentPagerFragmentDoestroy()
super.onDestroy()
}
}
and in the hosting activity
class HostActivity : AppCompatActivity(),
ContentPagerFragment.RemoveFragmentAtClose {
override fun onContentPagerFragmentDoestroy() {
supportFragmentManager.beginTransaction().
remove(supportFragmentManager.findFragmentById(R.id.pagerFragmentContainer))
.commitAllowingStateLoss()
var contentPagerFragment = ContentPagerFragment::class.java.cast(supportFragmentManager
.findFragmentById(R.id.pagerFragmentContainer))
Log.i(TAG, " onContentPagerFragmentDoestroy(), this $this" +
"\ngetContentPagerFragment: $contentPagerFragment"
)
}
override fun onAttachFragment(fragment: Fragment) {
Log.d(TAG, " onAttachFragment(), " +
"fragment: $fragment \nthis $this")
}
}
But the log shows that the supportFragmentManager.findFragmentById(R.id.pagerFragmentContainer) still find the fragment after the .commitAllowingStateLoss() call.
And when os restores the activity the ContentPagerFragment is showing up in the activity's onAttachFragment(fragment: Fragment) before activity's onCreate(savedInstanceState: Bundle?) is called.
Why is the viewPager using the old fragment after remove the pager fragment from the activity's supportFragmentManager?
How to prevent the view pager from use the cached fragment?

Related

Activities vs Fragments for apps

I have been trying to figure this out for a bit now. I see a lot of developers and youtubers (tutorials) saying that we should use as little activities as possible in order for a faster, more efficient and less resource heavy code/app. I was wondering if there is a way to create a Log-in and Sign-up using only the MainActivity for both Log-in and Sign-Up in combination with fragments for navigation between them.
Or
Do we need atleast 2 or more activities to handle that process ( Log-in & Sign-Up )?
Example: 1 activity for log-in and 1 activity for sign-up.
Appreciate and welcome any answers regarding this topic!
Theoretically, you could have your entire application run on a single Activity and use Fragments for all of the pages.
Each fragment has its own lifecycle within the activity.
MainActivity can look like this
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
loadFragment(2)
}
public fun loadFragment(page: Int){
if(page == 1){
// load login
val manager = supportFragmentManager.beginTransaction()
manager.replace(R.id.fragment_holder, LoginFragment()).commit()
}else{
// load register
val manager = supportFragmentManager.beginTransaction()
manager.replace(R.id.fragment_holder, LoginFragment()).commit()
}
}
}
LoginFragment can look like this
class LoginFragment : Fragment() {
lateinit var myButton: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_login, container, false)
myButton = view.findViewById(R.id.my_button)
myButton.apply {
setOnClickListener { toRegister() }
}
return view;
}
fun toRegister(){
// replace the fragment in main activity with register fragment
(requireActivity() as MainActivity).loadFragment(1)
}
}
RegisterFragment can look like this
class RegisterFragment : Fragment() {
lateinit var mButton: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_register, container, false)
mButton = view.findViewById(R.id.my_button)
mButton.apply {
setOnClickListener { toLogin() }
}
return view;
}
fun toLogin(){
// replacing the fragment in the main activity with the login fragment
(requireActivity() as MainActivity).loadFragment(1)
}
}
Basically, we replace the fragment displayed in the activity by calling loadfragment.
This logic can be applied to as many fragments as is necessary.

how to navigate to fragment inside recycler view?

I have an activity that is controlled with a navigation component, it has few fragments, inside one of these fragments there is a recyclerView that has some items, when I click on an Item I want it to navigate me to another fragment that has additional information about the item, I don't know how to use navigation component inside a recycelerView, when I type findNavController it has some parameters that am not sure what to put in or if its even the right function, I also tried to do it by code like this:
val fm = (context as AppCompatActivity).supportFragmentManager
fm.beginTransaction()
.replace(R.id.fragmentContainer, fragment)
.addToBackStack(null)
.commit()
by the way this is the code that asks for other parameters:
// it asks for a (fragment) or (activity, Int)
findNavController().navigate(R.id.action_settingsFragment_to_groupUnits)
the problem is when I navigate out of this fragment or use the drawer navigation (nav component for the other fragments), this fragment that I navigated to stays displayed in the screen, I see both fragments at the same time, I assume its a fragment backStack issue but I don't know how to solve it, thanks for the help and your time in advance
You do not need to navigate from RecyclerView item click to AdditionalDetails fragment directly.
You can do this same thing by help of interface.
Steps:
Create an interface with a method declaration.
Extend Interface from the fragment where you are using your RecyclerView and Implement interface method.
Pass this interface with the adapter.
Using the interface from adapter you just pass object when click on item.
Finally from your fragment you just navigate to AdditionalDetails fragment with argument.
Lets see sample code from my current project:
Interface
interface ChatListClickListener {
fun onChatListItemClick(view:View, user: User)
}
Adapter Class
class UserAdapter(val Users: List<User>, val chatListClickListener: ChatListClickListener) : RecyclerView.Adapter<UserAdapter.UserViewHolder>() {
inner class UserViewHolder(
val recyclerviewUsersBinding: RecyclerviewChatlistBinding
) : RecyclerView.ViewHolder(recyclerviewUsersBinding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val vh = UserViewHolder(
DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.recyclerview_chatlist,
parent,
false
)
)
return vh
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
holder.recyclerviewUsersBinding.user = Users[position]
holder.recyclerviewUsersBinding.root.setOnClickListener{
chatListClickListener.onChatListItemClick(it,Users[position])
}
}
override fun getItemCount(): Int {
return Users.size
}
}
My fragment
class FragmentChatList : Fragment(), ChatListClickListener {
lateinit var binding: FragmentChatListBinding
lateinit var viewModel: ChatListViewModel
lateinit var listener: ChatListClickListener
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val args: FragmentChatListArgs by navArgs()
binding = FragmentChatListBinding.inflate(layoutInflater, container, false)
val factory = ChatListFactory(args.user)
viewModel = ViewModelProvider(this, factory).get(ChatListViewModel::class.java)
binding.viewModel = viewModel
listener = this
lifecycleScope.launch {
viewModel.addUserWhenUserConnect()
}
viewModel.userList.observe(viewLifecycleOwner, Observer { data ->
binding.rvChatList.apply {
layoutManager = LinearLayoutManager(requireContext())
setHasFixedSize(true)
adapter = UserAdapter(data, listener)
}
})
return binding.root
}
override fun onChatListItemClick(view: View, user: User) {
Toast.makeText(requireContext(), user.name + "", Toast.LENGTH_SHORT).show()
// here you navigate to your fragment....
}
}
I guess this will be helpful.

onMapReady function seems to not be called on android app

The map seems to be created but the marker does not get placed. Most questions relating to this problem seems to be missing getmapasync but th,s one seems to have it
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Retrieve the content view that renders the map.
setContentView(R.layout.fragment_dashboard)
val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as? SupportMapFragment
mapFragment?.getMapAsync(this)
}
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
val sydney = LatLng(-33.852, 151.211)
mMap.addMarker(
MarkerOptions()
.position(sydney)
.title("Marker in Sydney")
)
// [START_EXCLUDE silent]
mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney))
// [END_EXCLUDE]
}
// [END maps_marker_on_map_ready_add_marker]
Is your map fragment nested in a fragment?
If you are calling getMapAsync from a fragment that contains the actual map, you need to use childFragmentManager instead of supportFragmentManager to find the map
(childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment).getMapAsync(this)

why onClickListener does not work in my fragment activity?

Im new in programming, and i got stuck with adding onClickListener in my FragmentHome.kt
i added this code to my existing activity:
val exc = this.findViewById<Button>(R.id.execute)
exc.setOnClickListener {
Toast.makeText(this, "You clicked me.", Toast.LENGTH_SHORT).show()
}
I tried set onClicklistener on a blank activity and it worked, but when i added it to an existing
Fragment activity it does nothing (it should display a toast with some text)
I see no error messages so i don't know where the problem could be.
Thank you for your responses.
enter code here
public class FragmentHome : Fragment() {
public class HomeFragmentElements : AppCompatActivity() {
private lateinit var spinView: Spinner
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.fragment_home)
val exc = this.findViewById<Button>(R.id.execute)
exc.setOnClickListener {
Toast.makeText(this, "You clicked me.", Toast.LENGTH_SHORT).show()
}
spinView = findViewById(R.id.spinner)
spinView.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
TODO("Not yet implemented")
}
override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("Not yet implemented")
}
}
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_home, container, false)
}
}
enter code here
Try to put your code into "onViewCreated":
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val exc = this.findViewById<Button>(R.id.execute)
exc.setOnClickListener {
Toast.makeText(this, "You clicked me.", Toast.LENGTH_SHORT).show()
}
}
onViewCreated is executed after "onCreateView". View bindings and initializations should be into onViewCreated
change the following in your onCreateView
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view: View = inflater.inflate(R.layout.fragment_home, container, false)
collectId(view)
return view
}
fun collectId(view: view){
val exc = view.findViewById<Button>(R.id.execute)
exc.setOnClickListener {
Toast.makeText(this, "You clicked me.", Toast.LENGTH_SHORT).show()
}
}
You can consider viewBinding instead of findViewById
It looks like you have defined an AppCompatActivity subclass inside your Fragment class's definition, kind of like this:
class MyFragment: Fragment {
class MyActivity: AppCompatActivity() {
//...
}
}
The ability to define a non-inner class in a nested way is purely a code organization tool. There is absolutely no connection between your Fragment and Activity classes here. The code above is no different than if you did this, with each class in a completely different file:
class MyFragment: Fragment {
}
class MyActivity: AppCompatActivity() {
}
It also doesn't make sense to think of an Activity as a child of a Fragment. A Fragment is a piece of an Activity, so it's the other way around.
All the code that you have in this Activity's onCreate should be put in the Fragment's onViewCreated() function, except for the call to setContentView(), because that's what your overriding of onCreateView() does. And remove the unused nested Activity class.
Also, you don't need to override onCreateView if you're simply inflating a layout and returning it. You can put the layout directly in the super-constructor call like this:
public class FragmentHome : Fragment(R.layout.fragment_home) {
private lateinit var spinView: Spinner
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// your view setup code
}
}

Context in fragment for recyclerview - Kotlin

I should be able to resolve this as their are so many similar posts on Stack but my brain is just not getting it...
I'm fairly new to android (from flutter) and I want a recyclerview in a fragment... moreover the recycler view doesn't use XML but a custom view class created by some JSON/GSON. None of the online tutorials really cover this and the Stack articles deal with one or another part but not all combined.
I think I have setup my custom views and adapter ok, but no matter what I try I am getting errors from my fragment, mostly related to the context and null parameters.
Here is my fragment class:
class MySquibsFragment : Fragment() {
//var squibGrid: RecyclerView = RecyclerView(requireContext())
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val v: View = inflater.inflate(R.layout.fragment_my_squibs, container, false)
//squibGrid = v.findViewById<RecyclerView>(R.id.squibgrid)
return v
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val squibList: ArrayList<SquibModel> = ArrayList()
try {
val jsonString = getJSONFromAssets()!!
val squibs = Gson().fromJson(jsonString, Squibs::class.java)
//squibGrid.layoutManager = LinearLayoutManager(activity)
//val itemAdapter = MySquibsAdapter(requireActivity(), squibs.squibs)
//squibGrid.adapter = itemAdapter
} catch (e: JSONException) {
e.printStackTrace()
}
}
The parts that are commented out are the lines that are throwing errors. I've tried moving all of onCreate into onActivityCreated and I've tried using lateinit to get the context, I just can't get my head around it.
Writing val squibGrid = view.findViewById<RecyclerView>(R.id.squibgrid) at the top means that you're trying to find the view when your fragment object is initialized. At that time, the fragment's view is not inflated, so you'll get an exception.
The fragment's view is inflated in onCreateView()
val v: View = inflater.inflate(R.layout.fragment_my_squibs, container, false)
return v
Try moving everything to onViewCreated(). According to the docs for fragment's lifecycle onViewCreated() is called after onCreateView(), i.e after the view is inflated.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val squibGrid = view.findViewById<RecyclerView>(R.id.squibgrid)
try {
val jsonString = getJSONFromAssets()!!
val squibs = Gson().fromJson(jsonString, Squibs::class.java)
squibGrid.layoutManager = LinearLayoutManager(activity)
val itemAdapter = MySquibsAdapter(requireActivity(), squibs.squibs)
squibGrid.adapter = itemAdapter
} catch (e: JSONException) {
e.printStackTrace()
}
}
Context in fragments will be available after onAttachContext method is called, also your views won't be created before onCreateView, it will crash if you try to access squibGrid in onCreate, You can safely bet on onViewCreated method where context and views are already available to consume
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val squibList: ArrayList<SquibModel> = ArrayList()
try {
val jsonString = getJSONFromAssets()!!
val squibs = Gson().fromJson(jsonString, Squibs::class.java)
//squibGrid.layoutManager = LinearLayoutManager(activity)
//val itemAdapter = MySquibsAdapter(requireActivity(), squibs.squibs)
//squibGrid.adapter = itemAdapter
} catch (e: JSONException) {
e.printStackTrace()
}
}