Fragments memory leaks - java

I have only one activity and all navigation is solved through fragments. If I open the fragment and come back, the fragment will be still in memory and memory will be growing.
What am I doing wrong? Do I have to manually remove listeners? In the onDestroy method, getView is already null.
How I add fragments:
fun replaceFragment(
fragmentManager: FragmentManager?,
fragment: Fragment?,
frameId: Int,
tag: String?
) {
if (fragmentManager != null && fragment != null) {
val previousFragment = fragmentManager.findFragmentById(frameId)
if (fragment != previousFragment) {
val transaction = fragmentManager.beginTransaction()
transaction.replace(frameId, fragment, tag)
transaction.addToBackStack(tag)
transaction.commitAllowingStateLoss()
}
}
}
Fragment close by the back button:
EDIT:
How I create Fragment
companion object {
fun create(number: Long): ManageUnitFragment {
val fragment = ManageUnitFragment()
val params = Bundle()
params.putLong(Keys.Number, number)
fragment.arguments = params
return fragment
}
}
How I set Listeners (on buttons)
import kotlinx.android.synthetic.main.myFragment.*
vMyButton.setOnClickListener {
myAction()
}
EDIT
If I remove this line vButtonClose.setOnClickListener... , the ManageUnitFragments will no longer be in the heap dump.
ManageUnitFragment:
import kotlinx.android.synthetic.main.fragment_manage_unit.*
class ManageUnitFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_manage_unit, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
vButtonClose.setOnClickListener {
activity?.onBackPressed()
}
}
companion object {
fun create(unitNumber: Long, wagonWagonUnit: WagonUnit): ManageUnitFragment {
val fragment = ManageUnitFragment()
return fragment
}
}
}

Have you tried to add 'many' fragments? Do you actually get OutOfMemoryException?
Maybe they just remain in memory until more of it is needed. Then they will be garbage collected.

Related

Fatal Exception: java.lang.NullPointerException crash event [duplicate]

This question already has answers here:
What is a NullPointerException, and how do I fix it?
(12 answers)
Closed last month.
An error occurs on line 41 when initializing the SongListFragment fragment class.
Here is a snippet of the class:
class SongListFragment : Fragment() {
private var _binding: FragmentSongsListBinding? = null
private val binding get() = _binding!! //error
private var player: ExoPlayer? = null
private lateinit var inputManager: InputMethodManager
private lateinit var sharedPreferences: SharedPreferences
private lateinit var editor: SharedPreferences.Editor
private val songListAdapter by lazy {
SongListAdapter(requireActivity()) { show ->
showMultipleSelectActionBar(
show
)
}
}
private val viewModel: SongListViewModel by activityViewModels()
#SuppressLint("RtlHardcoded")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
player = (context as MainActivity).exoPlayer
inputManager =
requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
editor = sharedPreferences.edit()
enterTransition = Slide(Gravity.END)
exitTransition = Fade()
returnTransition = Fade()
}
#RequiresApi(Build.VERSION_CODES.Q)
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
requireActivity().window.statusBarColor =
ContextCompat.getColor(requireContext(), R.color.main_background_color)
if (_binding == null) {
_binding = FragmentSongsListBinding.inflate(inflater, container, false)
}
postponeEnterTransition()
binding.root.doOnPreDraw { startPostponedEnterTransition() }
viewModel.getSongList(context)
initObserver()
setDataToView()
searchMusic()
multipleSelectButtonImplementation()
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (sharedPreferences.getInt(Constants.AD_COUNTER, 0) == 3) {
(activity as MainActivity).showAd(requireContext())
editor.putInt(Constants.AD_COUNTER, 0)
editor.commit()
} else {
editor.putInt(Constants.AD_COUNTER, (sharedPreferences.getInt(Constants.AD_COUNTER, 0) + 1))
editor.commit()
}
}
stack trace:
Fatal Exception: java.lang.NullPointerException
example.androidvolumelouder.presentation.ui.fragments.songlistfragment.SongListFragment.getBinding (SongListFragment.kt:41)
example.androidvolumelouder.presentation.ui.fragments.songlistfragment.SongListFragment.access$getBinding (SongListFragment.kt:38)
example.androidvolumelouder.presentation.ui.fragments.songlistfragment.SongListFragment$setSongCount$1.invokeSuspend (SongListFragment.kt:225)
kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33)
kotlinx.coroutines.DispatchedTaskKt.resume (DispatchedTask.kt:234)
kotlinx.coroutines.DispatchedTaskKt.dispatch (DispatchedTask.kt:166)
kotlinx.coroutines.CancellableContinuationImpl.dispatchResume (CancellableContinuationImpl.kt:397)
kotlinx.coroutines.CancellableContinuationImpl.resumeImpl (CancellableContinuationImpl.kt:431)
kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default (CancellableContinuationImpl.kt:420)
kotlinx.coroutines.CancellableContinuationImpl.resumeUndispatched (CancellableContinuationImpl.kt:518)
kotlinx.coroutines.android.HandlerContext$scheduleResumeAfterDelay$$inlined$Runnable$1.run (Runnable.kt:19)
This happens on the first run after installation. And what's more, everything is fine on physical devices with Android 10 on all others and on emulators as well.
What is the error, please help?
The crash is not in the above lines.. However, you are accessing binding after you navigate the screen.
You need to safe call isAdded before using binding as follow: e.g.
if(isAdded){
binding.button.setOnClickListener{ ... }
}
Try to move your UI elements bind logic to onViewCreated
See Fragment  |  Android Developers
Called immediately after onCreateView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle) has returned, but before any saved state has been restored in to the view. This gives subclasses a chance to initialize themselves once they know their view hierarchy has been completely created. The fragment's view hierarchy is not however attached to its parent at this point.
For instance:
class SongListFragment : Fragment() {
private var _binding: FragmentSongsListBinding? = null
private val binding get() = _binding!! //error
private var player: ExoPlayer? = null
private lateinit var inputManager: InputMethodManager
private lateinit var sharedPreferences: SharedPreferences
private lateinit var editor: SharedPreferences.Editor
private val songListAdapter by lazy {
SongListAdapter(requireActivity()) { show ->
showMultipleSelectActionBar(
show
)
}
}
private val viewModel: SongListViewModel by activityViewModels()
#SuppressLint("RtlHardcoded")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
player = (context as MainActivity).exoPlayer
inputManager =
requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
editor = sharedPreferences.edit()
enterTransition = Slide(Gravity.END)
exitTransition = Fade()
returnTransition = Fade()
}
#RequiresApi(Build.VERSION_CODES.Q)
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
requireActivity().window.statusBarColor =
ContextCompat.getColor(requireContext(), R.color.main_background_color)
if (_binding == null) {
_binding = FragmentSongsListBinding.inflate(inflater, container, false)
}
postponeEnterTransition()
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.root.doOnPreDraw { startPostponedEnterTransition() }
viewModel.getSongList(context)
initObserver()
setDataToView()
searchMusic()
multipleSelectButtonImplementation()
if (sharedPreferences.getInt(Constants.AD_COUNTER, 0) == 3) {
(activity as MainActivity).showAd(requireContext())
editor.putInt(Constants.AD_COUNTER, 0)
editor.commit()
} else {
editor.putInt(Constants.AD_COUNTER, (sharedPreferences.getInt(Constants.AD_COUNTER, 0) + 1))
editor.commit()
}
}

Implement Geojson points to map and locate me with Mapbox, in Kotlin on Android Studio

My question is about Mapbox. In this period I am working on an ANDROID application based on mapbox, using Kotlin and Fragments and my problem concerns the visualization of points on the map itself. That is my need is to be able to show points on the map through a GEOJSON file, for now I have been able to see the map in full in the application, but I cannot find a way to show the points taken from a GeoJson file and locate myself in the map via a button.
I should implement both functions in the fragment, so my problem is precisely that of not being able to show the points of a geojson file and find a way to locate myself in the map itself. I await help if there is someone able to help me with this problem, I also leave the code of the fragment class in kotlin.
Thanks everyone in advance !!
FRAGMENT HOME
class HomeFragment : Fragment() {
private var mapView: MapView? = null
#Nullable
override fun onCreateView(
inflater: LayoutInflater,
#Nullable container: ViewGroup?,
#Nullable savedInstanceState: Bundle?
): View? {
Mapbox.getInstance(
context!!.applicationContext,
"MIO CODICE MAPBOX"
)
val view: View = inflater.inflate(R.layout.fragment_home, container, false)
mapView = view.findViewById<View>(R.id.mapView) as MapView
mapView!!.onCreate(savedInstanceState)
return view
}
override fun onResume() {
super.onResume()
mapView!!.onResume()
}
override fun onPause() {
super.onPause()
mapView!!.onPause()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
mapView!!.onSaveInstanceState(outState)
}
override fun onLowMemory() {
super.onLowMemory()
mapView!!.onLowMemory()
}
override fun onDestroyView() {
super.onDestroyView()
mapView!!.onDestroy()
}
}
HOST ACTIVITY :
class HostActivity : AppCompatActivity() {
lateinit var googleSignInClient: GoogleSignInClient
private lateinit var navController: NavController
private val mAuth: FirebaseAuth = FirebaseAuth.getInstance()
private val db: FirebaseFirestore = FirebaseFirestore.getInstance()
private lateinit var drawerLayout: DrawerLayout
private lateinit var navViewBinding: DrawerHeaderLayoutBinding
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.AppTheme)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_host)
val toolbar = customToolbar
setSupportActionBar(toolbar)
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getString(R.string.default_web_client_id))
.requestEmail()
.build()
googleSignInClient = GoogleSignIn.getClient(this, gso)
drawerLayout = drawer_layout
navViewBinding = DrawerHeaderLayoutBinding.inflate(layoutInflater, navView, true)
val navHost =
supportFragmentManager.findFragmentById(R.id.navHostFragment) as NavHostFragment
navController = navHost.navController
val navInflater = navController.navInflater
val graph = navInflater.inflate(R.navigation.main_graph)
navController.addOnDestinationChangedListener { _, destination, _ ->
if (destination.id == R.id.onBoarding ||
destination.id == R.id.authFragment ||
destination.id == R.id.loginFragment ||
destination.id == R.id.signUpFragment
) {
toolbar.visibility = View.GONE
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
} else {
toolbar.visibility = View.VISIBLE
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
}
}
if (!Prefs.getInstance(this)!!.hasCompletedWalkthrough!!) {
if (mAuth.currentUser == null) {
graph.startDestination = R.id.authFragment
} else {
getUserData()
graph.startDestination = R.id.homeFragment
}
} else {
graph.startDestination = R.id.onBoarding
}
navController.graph = graph
NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)
navView.setupWithNavController(navController)
navView.setNavigationItemSelectedListener {
it.isChecked
drawerLayout.closeDrawers()
when (it.itemId) {
R.id.action_logout -> {
MyApplication.currentUser!!.active = false
FirestoreUtil.updateUser(MyApplication.currentUser!!) {
mAuth.signOut()
}
googleSignInClient.signOut()
MyApplication.currentUser = null
navController.navigate(R.id.action_logout)
}
}
true
}
}
private fun getUserData() {
val ref = db.collection("users").document(mAuth.currentUser!!.uid)
ref.get().addOnSuccessListener {
val userInfo = it.toObject(UserModel::class.java)
navViewBinding.user = userInfo
MyApplication.currentUser = userInfo
MyApplication.currentUser!!.active = true
FirestoreUtil.updateUser(MyApplication.currentUser!!) {
}
}.addOnFailureListener {
val intent = Intent(this, MyApplication::class.java)
startActivity(intent)
finish()
}
}
override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(navController, drawerLayout)
}
}
You'll need to use a GeoJsonSource. https://github.com/mapbox/mapbox-android-demo/search?q=GeoJsonSource shows how the demo app uses the source.
https://github.com/mapbox/mapbox-android-demo/blob/master/MapboxAndroidDemo/src/main/java/com/mapbox/mapboxandroiddemo/examples/basics/KotlinSupportMapFragmentActivity.kt (Its XML layout file)
Putting icons on the map. https://docs.mapbox.com/android/maps/examples/marker-symbol-layer/. Do all of the icon setup inside of the fragment's onStyleLoaded() callback as seen at https://github.com/mapbox/mapbox-android-demo/blob/master/MapboxAndroidDemo/src/main/java/com/mapbox/mapboxandroiddemo/examples/basics/KotlinSupportMapFragmentActivity.kt#L51-L55
https://github.com/mapbox/mapbox-android-demo/search?q=loadGeojson shows how the demo app loads from a GeoJson file. You could use coroutines instead of building out the AsyncTask.
Although it's in Java, https://docs.mapbox.com/android/maps/examples/show-a-users-location-on-a-fragment/ shows how to combine the Maps SDK's LocationComponent with a fragment.
Regarding moving the camera to the device's last known location when a button is clicked, see my answer at https://stackoverflow.com/a/64159178/6358488

How to notify and update list when item has been deleted using Groupie RecyclerView library and Kotlin

I have a RecyclerView implemented with the Groupie library and I can delete an item from the list fine, however need to update the view to see the change. I'd like to have something like notifyDataSetChanged() instead, so the list updates immediately. I'm a bit confused at this stage though, tried a few different ways to get an interface from the class that hosts my view holder to be triggered from the fragment that holds the adapter but I think I'm stuck now if I could get some help please.
class RecyclerProductItem(
private val activity: MainActivity,
private val product: Product, private val adapterListener: AdapterListener
) : Item<GroupieViewHolder>() {
companion object {
var clickListener: AdapterListener? = null
}
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.apply {
with(viewHolder.itemView) {
clickListener = adapterListener
ivTrash.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
if (clickListener != null) {
Toast.makeText(context, "delete method to be added here", Toast.LENGTH_SHORT).show()
clickListener?.onClickItem(position)
}
}
})
}
}
}
override fun getLayout() = R.layout.recyclerview_item_row
interface AdapterListener {
fun onClickItem(position: Int)
}
}
Here it's my fragment. I tried to add a section to the adapter to see if it would allow me to retrieve a listener for it, but as my listener should be triggered under a specific item within the layout, this may not be the best solution, although couldn't make this work either.
class ProductsListFragment : Fragment(), RecyclerProductItem.AdapterListener {
private lateinit var adapter: GroupAdapter<GroupieViewHolder>
private val section = Section()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_products_list, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val linearLayoutManager = LinearLayoutManager(activity)
recyclerView.layoutManager = linearLayoutManager
adapter = GroupAdapter()
adapter.add(section)
recyclerView.adapter = adapter
loadProducts()
}
private fun loadProducts() {
GetProductsAPI.postData(object : GetProductsAPI.ThisCallback {
override fun onSuccess(productList: List<JsonObject>) {
for (jo in productList) {
val gson = GsonBuilder().setPrettyPrinting().create()
val product: Product =
gson.fromJson(jo, Product::class.java)
adapter.add(
RecyclerProductItem(
activity as MainActivity,
Product(
product.id,
product.title,
product.description,
product.price
),adapterListenerToBePassedHere
)
) // This part is where I should be giving the listener, but get a red line since not sure how to get it to include it here.
}
}
})
}
companion object {
fun newInstance(): ProductsListFragment {
return ProductsListFragment()
}
}
override fun onClickItem(position: Int) {
adapter.notifyItemRemoved(position)
}
}
Many thanks.
I think you are missing this concept from the groupie Readme:
Modifying the contents of the GroupAdapter in any way automatically sends change notifications. Adding an item calls notifyItemAdded(); adding a group calls notifyItemRangeAdded(), etc.
So to remove an item, call section.remove(item). However, in your onClickItem function you currently only pass the position. Pass the item like clickListener?.onClickItem(this#RecyclerProductItem) instead.
Even more ideally and safely you should remove by product.id, e.g. clickListener?.onClickItem(this#RecyclerProductItem.product.id) then in onClickItem() you just search for the item with that product id and remove it. Let me know if I'm not clear.
Based on #carson's reply, this is what worked for me. Had to add the items to the section, the section to the adapter and then remove the item from the section based on the adapter position once that listener is clicked, passing the method that implements the listener as one of the arguments to complete the GroupAdapter.
class RecyclerProductItem(
private val activity: MainActivity,
private val product: Product, private val adapterListener: AdapterListener
) : Item<GroupieViewHolder>() {
companion object {
var clickListener: AdapterListener? = null
}
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.apply {
with(viewHolder.itemView) {
tvTitle.text = product.title
clickListener = adapterListener
ivTrash.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
if (clickListener != null) {
Toast.makeText(context, "delete method to be added here", Toast.LENGTH_SHORT).show()
clickListener?.onClickItem(this#RecyclerProductItem.product.id, adapterPosition)
}
}
})
}
}
}
override fun getLayout() = R.layout.recyclerview_item_row
interface AdapterListener {
fun onClickItem(id: Int, position: Int)
}
}
And
private fun loadProducts() {
GetProductsAPI.postData(object : GetProductsAPI.ThisCallback,
RecyclerProductItem.AdapterListener {
override fun onSuccess(productList: List<JsonObject>) {
Log.i(LOG_TAG, "onSuccess $LOG_TAG")
for (jo in productList) {
val gson = GsonBuilder().setPrettyPrinting().create()
val product: Product =
gson.fromJson(jo, Product::class.java)
val linearLayoutManager = LinearLayoutManager(activity)
recyclerView.layoutManager = linearLayoutManager
adapter = GroupAdapter()
section.add(
RecyclerProductItem(
activity as MainActivity,
Product(
product.id,
product.title,
product.description,
product.price
), this
)
)
adapter.add(section)
recyclerView.adapter = adapter
}
}
override fun onFailure() {
Log.e(LOG_TAG, "onFailure $LOG_TAG")
}
override fun onError() {
Log.e(LOG_TAG, "onError $LOG_TAG")
}
override fun onClickItem(id: Int, position: Int) {
section.remove(adapter.getItem(position))
}
})
}

View pager holds old fragment after rotate screen

i have a view pager, with 4 fragments. (im using FragmentStatePagerAdapter)
Each fragment have a "FrameLayout/ container", where i add and replace many fragments.
Everything works fine, but when i change screen orientation, the first fragment of the line is restored Over actual fragment. Then both appears at same time.
Im putting a picture with a example of what happens:
First Fragment
Second Fragment
[when i rotate screen]
http://i.stack.imgur.com/kxaVn.png
My MainActivity Code
class MainActivity : AppCompatActivity() {
val numPages = 4
var pager: ViewPager? = null
private val TITLES = arrayOf("Feed", "Catalogo","Guia","Rendimento")
var menuImages:Array<ImageView>?=null
var menuTexts:Array<TextView>?=null
var fragments = arrayListOf<Fragment>()
private var fragmentCreated1: FeedContainerFragment? = null
private var fragmentCreated2: CatalogContainerFragment? = null
private var fragmentCreated3: GuideContainerFragment? = null
private var fragmentCreated4: TestContainerFragment? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_default)
fragments.add(FeedContainerFragment())
fragments.add(CatalogContainerFragment())
fragments.add(GuideContainerFragment())
fragments.add(TestContainerFragment())
menuImages= arrayOf(findViewById(R.id.icon_feed) as ImageView,
findViewById(R.id.icon_catalogue) as ImageView,
findViewById(R.id.icon_guide) as ImageView,
findViewById(R.id.icon_form) as ImageView)
menuTexts= arrayOf(findViewById(R.id.text_feed) as TextView,
findViewById(R.id.text_catalogue) as TextView,
findViewById(R.id.text_guide) as TextView,
findViewById(R.id.text_form) as TextView)
menuImages?.get(0)?.setColorFilter(ContextCompat.getColor(baseContext,R.color.colorAccent))
menuTexts?.get(0)?.setTextColor(ContextCompat.getColor(baseContext,R.color.colorAccent))
//view pager
pager = findViewById(R.id.pager) as ViewPager
val pagerAdapter = ScreenSlidePagerAdapter(supportFragmentManager)
pager?.adapter = pagerAdapter
pager?.offscreenPageLimit = 4
pager?.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
}
override fun onPageSelected(position: Int) {
pager?.adapter?.notifyDataSetChanged()
repaintMenuDefaultColor()
menuImages?.get(position)?.setColorFilter(ContextCompat.getColor(baseContext,R.color.colorAccent))
menuTexts?.get(position)?.setTextColor(ContextCompat.getColor(baseContext,R.color.colorAccent))
}
override fun onPageScrollStateChanged(state: Int) {
}
})
//Navigation Menus
MenuUtils.generateMaterialMenu(this)
MenuUtils.generateBottomMenu(this, this.pager!!)
}
private inner class ScreenSlidePagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
override fun getCount(): Int {
return numPages
}
override fun getItem(position: Int): Fragment {
when(position) {
0->return FeedContainerFragment()
1->return CatalogContainerFragment()
2->return GuideContainerFragment()
3->return TestContainerFragment()
else->return FeedContainerFragment()
}
}
override fun getPageTitle(position: Int): CharSequence {
return TITLES[position]
}
override fun getItemPosition(obj: Any?): Int {
if (obj is Refreshable) {
return POSITION_NONE
}
return super.getItemPosition(obj)
}
override fun instantiateItem(container:ViewGroup, position:Int):Any {
val createdFragment = super.instantiateItem(container, position) as Fragment
when (position) {
0 -> fragmentCreated1 = createdFragment as FeedContainerFragment
1 -> fragmentCreated2 = createdFragment as CatalogContainerFragment
2 -> fragmentCreated3 = createdFragment as GuideContainerFragment
3 -> fragmentCreated4 = createdFragment as TestContainerFragment
}
return createdFragment
}
}
override fun onBackPressed() {
if (fragmentManager.backStackEntryCount > 0) {
fragmentManager.popBackStack()
} else {
super.onBackPressed()
}
}
fun repaintMenuDefaultColor(){
this.menuImages?.map {
it.setColorFilter(ContextCompat.getColor(baseContext,R.color.menu_icon))
}
this.menuTexts?.map {
it.setTextColor(ContextCompat.getColor(baseContext,R.color.menu_text))
}
}
}
Any Help is Welcome !
EDIT: Actually, what is happening is that ViewPager keep the Actual Fragment as a "Ghost" and then restore the First Fragment of the line.
Is there a way to clean the screen and remove that "Ghost" ?
EDIT 2: Found the solution. I needed to verify if a instance of fragment already exists before add the fragment on each "container"
The problem wasn't on ViewPager, or Adapter. Every time i change the orientation a new fragment was included.
So, just put the verification below :
if(savedInstanceState == null) {
activity
.supportFragmentManager
.beginTransaction()
.add(R.id.containerGuide, YourFragment())
.commit()
}
When Rotating Screen the Activity and the Fragments are recreated so its start from position 0..
Save Fragment position when viewpager is rebuild just choose the Fragment you need.
Check out this link https://developer.android.com/guide/topics/resources/runtime-changes.html
Disable screen Rotations.

notifyDataSetChanged() doesn't call's onBindViewHolder() method

I have a RecyclerView's Adapter, where I am adding items dynamically, when I am calling the my adapter's updateMessages function old data list were changing correctly but, recycler items stays the same.
this is my updateMessages method in my adapter:
fun updateMessages(messages: List<MessageReceivedResponseModel>?){
messages?.let {
this.messages.clear()
this.messages.addAll(messages)
}
notifyDataSetChanged()
}
also here is complete adapter class, I don't understand what's the problem
class MessagesRecyclerAdapter(private val itemActionListener: IOnMessageItemActionListener) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val messages = ArrayList<MessageReceivedResponseModel>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.awesome_chat_item, parent, false)
return MyHolder(view)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder as MyHolder).updateUI(messages[position])
}
override fun getItemCount(): Int {
return messages.size
}
fun updateMessages(messages: List<MessageReceivedResponseModel>?){
messages?.let {
this.messages.clear()
this.messages.addAll(messages)
}
notifyDataSetChanged()
}
private inner class MyHolder internal constructor(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val messageTextView: TextView = itemView.findViewById(R.id.chat_from_text_id) as TextView
private val msgQuantityTextView: TextView = itemView.findViewById(R.id.msg_quantity_txt_id) as TextView
internal fun updateUI(msg: MessageReceivedResponseModel) {
messageTextView.text = msg.from
msgQuantityTextView.text = msg.quantity.toString()
}
}
}
this is where my adapter and recycler initialization goes
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater?.inflate(R.layout.activity_awesome_chat, container, false)
recycler = view?.findViewById(R.id.awesome_chat_list_view_id) as RecyclerView
val layoutManager = LinearLayoutManager(activity)
recycler?.layoutManager = layoutManager
.....
}
override fun onMessageReceived(messages: List<MessageReceivedResponseModel>){
adapter?.updateMessages(messages)
Log.e(TAG, messages.size.toString())
}
// called in oncreateView of fragment
private fun initRecycler(items: List<MessageReceivedResponseModel>?) {
adapter = MessagesRecyclerAdapter(this)
adapter?.updateMessages(items)
recycler?.adapter = adapter
Log.e(TAG, items?.size.toString())
}
I realized my problem where out of all this classes, The problem was in my interactor class where the messages retrieving requests started
fun startMessageRetrieveRequest(callback: OnRequestFinishedListener<List<MessageReceivedResponseModel>>){
doAsync {
Thread.sleep(1000)
val idx = Random().nextInt(2)
val its: List<MessageReceivedResponseModel>
when(idx){
0 -> its = REs
1 -> its = RES_1
else -> its = RES_1
}
callback.onResponse(its)
}
}
I removed doAsync and works correctly, here callback.onResponse() is being called from Non-UI thread and it caused the problem Only the original thread that created a view hierarchy can touch its views., but not always. Also app weren't crashed and I missed the log
Try using notifyItemRangeChanged(0, messages.size - 1);.
fun updateMessages(messages: List<MessageReceivedResponseModel>?){
messages?.let {
this.messages.clear()
this.messages.addAll(messages)
this.notifyItemRangeChanged(0, messages.size - 1)
}
}
I think notifyDataSetChanged() isn't working because your dataset (messages field) is still the same object.
You can try to replace object of messages:
fun updateMessages(messages: List<MessageReceivedResponseModel>?){
messages?.let {
this.messages = messages
notifyDataSetChanged()
}
}
You should use notifyDataSetChanged(); on your adapter, like this:
adapter.notifyDataSetChanged(); .
If you wanna use a class you can, you just need to declare the adapter variable as a member variable of the class and call the method like I said above. Where adapter is the name of your adapter.

Categories