Update Fragment inside ViewPager when Tab Reselect - android

This question has been asked a lot but in each solution i can't find a good way to implement it.
Basically i have my FragmentPagerAdaper
internal class FragmentPagerAdapter(fm: androidx.fragment.app.FragmentManager, private val mNumbOfTabs: Int) : androidx.fragment.app.FragmentStatePagerAdapter(fm) {
override fun getItem(position: Int): Fragment {
return when (position) {
HOME -> HomeFragment()
SHOPPING_CART -> ShoppingCartFragment()
/*COLLECTION -> CollectionFragment()
USER_SETTINGS -> UserSettingsFragment()*/
else -> HomeFragment()
}
}
override fun getCount(): Int {
return mNumbOfTabs
}
companion object {
private const val HOME = 0
private const val SHOPPING_CART = 1
private val COLLECTION = 1
private val USER_SETTINGS = 2
}
}
and the ShoppingCartFragment
class ShoppingCartFragment : Fragment() {
private var inputFragmentView: View? = null
var products: ArrayList<Any> = ArrayList()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val frameLayout = FrameLayout(this.context!!)
populateViewForOrientation(inflater,frameLayout)
return frameLayout
}
private fun RecyclerAnimator(recyclerView: RecyclerView, adapter: ProductCartViewAdapter) {
val itemAnimator = DefaultItemAnimator()
itemAnimator.addDuration = 1000
itemAnimator.removeDuration = 1000
recyclerView.itemAnimator = itemAnimator
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(context)
val divider = requireContext().resources.getDimensionPixelSize(R.dimen.divider_size_default)
recyclerView.addItemDecoration(DividerItemDecorator(divider, SPACE_BOTTOM))//SPACE_LEFT or SPACE_TOP or SPACE_RIGHT or SPACE_BOTTOM concat
}
fun checkList(){
products.clear()
products = database.retrieveShoppingCartList()
val emptyImage = inputFragmentView!!.findViewById<ImageView>(R.id.emptyList)
val recyclerList = inputFragmentView!!.findViewById<RecyclerView>(R.id.shopping_rv)
if (products.isEmpty() || products.size == 0){
recyclerList.visibility = GONE
recyclerList.removeAllViews()
emptyImage.visibility = VISIBLE
} else{
recyclerList.visibility = VISIBLE
emptyImage.visibility = GONE
val adapter = ProductCartViewAdapter(products, context!!, this)
RecyclerAnimator(recyclerList, adapter)
}
}
private fun populateViewForOrientation(inflater: LayoutInflater, viewGroup: ViewGroup) {
viewGroup.removeAllViewsInLayout()
inputFragmentView = inflater.inflate(R.layout.fragment_shopping_cart, viewGroup)
checkList()
}
}
until here everything goes well. This is how the app looks.
But when i reselect that Tab that contains the ShoppingCartFragment i need to refresh the list. to be more specific call the function FragmentPagerAdapter.checkList().
But each time that i try to call that function from the fragment i keep receiving a NullPointer due to the fragment context that cannot be found...
in this:
products = database.retrieveShoppingCartList()
and this is how i handle those context using a synchronizer in the getInstance
SQLiteHandler
private var instance: SQLiteHandler? = null
#Synchronized
fun getInstance(ctx: Context): SQLiteHandler {
if (instance == null) {
instance = SQLiteHandler(ctx.applicationContext)
}
return instance!!
}
// Access property for Context
val Context.database: SQLiteHandler
get() = SQLiteHandler.getInstance(applicationContext)
val androidx.fragment.app.Fragment.database: SQLiteHandler
get() = SQLiteHandler.getInstance(activity!!.applicationContext)
Or any way to recreate the fragment of the viewpager when the tab is reselected

Related

My recyclerview resets when I add or delete an item from a Table (with Room) in Kotlin

First of all, I am Spanish so my english is not good.
I have an app with Kotlin and room, and it has a Recyclerview.
I have 3 tables: coaster, user and favorite.
The user can add coasters to favorite, and this is done succesfully.
The problem that I have is that when the user clicks on the button to add or delete from favorites, the recyclerview resets, it displays again. So it scrolls to the top of the Screen, and also some odd spaces appears after the element.
I also have a function to search, and it happens the same: spaces appears after each element when I am searching.
I have tried everything: notifyItemChanged,
notifyDataSetChanged... it doesnt work! I also tried removing the observer once from the recyclerview...
My main activity:
class CoasterFragment : Fragment() {
lateinit var coasterListener: CoasterListener
lateinit var usuarioCoaster: List\<UsuarioCoaster\>
private lateinit var searchView: SearchView
private lateinit var cAdapter: CoasterRecyclerViewAdapter
private var \_binding: FragmentCoasterBinding? = null
private val binding get() = \_binding!!
private val viewModel: CoastersViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentCoasterBinding.inflate(inflater, container, false)
val root: View = binding.root
/* val livedata = viewModel.coasters()
livedata.observe(viewLifecycleOwner,object: Observer <List<CoasterFavorito>> {
override fun onChanged(it: List<CoasterFavorito>) {
createRecyclerView(it)
livedata.removeObserver(this)
}
})*/
viewModel.coasters().observe(viewLifecycleOwner){createRecyclerView(it)}
coasterListener = CoasterListenerImpl(requireContext(), viewModel)
searchView = binding.search
searchView.clearFocus()
searchView.setOnQueryTextListener(object: SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String?): Boolean {
if(query != null){
searchDatabase(query)
}
return true
}
override fun onQueryTextChange(query: String?): Boolean {
if(query != null){
searchDatabase(query)
}
return true
}
})
return root
}
fun createRecyclerView(coasters: List<CoasterFavorito>) {
cAdapter =
CoasterRecyclerViewAdapter(
coasters as MutableList<CoasterFavorito>,
coasterListener,
requireContext()
)
val recyclerView = binding.recyclerCoaster
recyclerView.apply {
layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
adapter = cAdapter
addItemDecoration(DividerItemDecoration(context, RecyclerView.VERTICAL))
cAdapter.notifyDataSetChanged()
}
}
fun searchDatabase(query: String) {
val searchQuery = "%$query%"
viewModel.searchDatabase(searchQuery).observe(viewLifecycleOwner) { createRecyclerView(it)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
my adapter:
class CoasterRecyclerViewAdapter(val coasters: List<CoasterFavorito>, val listener: CoasterListener,
val context: Context, ) : RecyclerView.Adapter<CoasterRecyclerViewAdapter.ViewHolder>(){
class ViewHolder private constructor(val binding: CoasterItemBinding, private val listener: CoasterListener,
private val context: Context): RecyclerView.ViewHolder(binding.root){
fun relleno(data: CoasterFavorito){
binding.nombre.text = data.coaster.nombre
binding.parque.text = data.coaster.parque
binding.ciudad.text = data.coaster.ciudad
binding.provincia.text = data.coaster.provincia
binding.comunidad.text = data.coaster.comunidadAutonoma
Glide
.with(context)
.load(data.coaster.imagen)
.centerCrop()
.into(binding.imagen)
binding.check.isChecked = data.favorito
binding.check.setOnClickListener{
if (data.favorito) {
listener.delFavorito(data.coaster.id)
binding.check.isChecked = false
} else {
listener.addFavorito(data.coaster.id)
binding.check.isChecked = true
}
}
}
companion object{
fun crearViewHolder(parent: ViewGroup, listener: CoasterListener, adapter: CoasterRecyclerViewAdapter, context: Context):ViewHolder{
val layoutInflater = LayoutInflater.from(parent.context)
val binding = CoasterItemBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding, listener, context )
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder.crearViewHolder(parent, listener, this, context)
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.relleno(coasters[position])
override fun getItemCount() = coasters.size
}
interface CoasterListener {
fun addFavorito(id: Long)
fun delFavorito(id: Long)
}
I have tried everything: notifyItemChanged,
notifyDataSetChanged... it doesnt work! I also tried removing the observer once from the recyclerview...
Your createRecyclerView function should be invoked only once in a whole lifecycle of the Fragment. You should not create any new RecyclerView.Adapter, or set a LayoutManager to the RecyclerView every time your data set changes.
Therefore the Observer used in viewModel.coasters.observe() should only submit a new List to the existing Adapter and call .notifyDataSetChanged(), or other notifying functions.

How to implement the ViewModel to save the state

So I have tried to implement a viewModel on my app, but it still operates the same way.
I want to save the current view of my game, but since the part that should be saved is inside a function that holds views, I couldn't move it.
Here's some of the code.
The Fragment
class GamePlay1Fragment : Fragment() {
// lateinit var front_anim: AnimatorSet
// lateinit var back_anim: AnimatorSet
private lateinit var viewModel: GP1ViewModel
private lateinit var pieces: List<ImageView>
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding: Gameplay1FragmentBinding = DataBindingUtil.inflate(
inflater,
R.layout.gameplay1_fragment,
container, false)
viewModel = ViewModelProvider(this).get(GP1ViewModel::class.java)
binding.backButtonView.setOnClickListener { v: View ->
v.findNavController().navigate(GamePlay1FragmentDirections.actionGamePlay1FragmentToLobbyFragment())
}
pieces = listOf(binding.card1back, binding.card2back, binding.card3back, binding.card4back,
binding.card5back,binding.card6back, binding.card7back,
binding.card8back, binding.card9back, binding.card10back, binding.card11back,
binding.card12back)
viewModel.gameCards = pieces.indices.map { index ->
GameCard(viewModel.images[index])
}
pieces.forEachIndexed { index, piece ->
piece.setOnClickListener {
viewModel.updatingModels(index)
updatingViews()
}
}
return binding.root
}
private fun updatingViews() {
viewModel.gameCards.forEachIndexed { index, gameCard ->
val piece = pieces[index]
piece.setImageResource(if (gameCard.isFacedUp) gameCard.id else allcardbacks)
}
}
}
The View-Model
class GP1ViewModel() : ViewModel() {
lateinit var gameCards: List<GameCard>
private var indexOfSelectedPiece: Int? = null
val images = mutableListOf(
R.drawable.memorybatcardfront,
R.drawable.memorycatcardfront,
R.drawable.memorycowcardfront,
R.drawable.memorydragonfront,
R.drawable.memorygarbagemancardfront,
R.drawable.memoryghostdogcardfront
)
init {
images.addAll(images)
images.shuffle()
Log.e(TAG, "ViewModel Created:")
}
fun updatingModels(position: Int) {
val gameCard = gameCards[position]
if (gameCard.isFacedUp) return
if (indexOfSelectedPiece == null) {
restoreGameCards()
indexOfSelectedPiece = position
}
else {
checkingForMatch(indexOfSelectedPiece!!, position)
indexOfSelectedPiece = null
}
gameCard.isFacedUp = !gameCard.isFacedUp
}
private fun restoreGameCards() {
val handler = Handler()
for (gameCard in gameCards) {
if (!gameCard.isMatched) {
handler.postDelayed(runnable, 1000)
gameCard.isFacedUp = false
}
}
}
private fun checkingForMatch(position1: Int, position2: Int) {
if (gameCards[position1].id == gameCards[position2].id) {
gameCards[position1].isMatched = true
gameCards[position2].isMatched = true
}
}
private val runnable = Runnable(){
kotlin.run {
}
}
}
The part I am assuming is that needs to be saved is the updatingView() in the fragment but I can't move it since it also dealing with an array of imageViews (S.O.C). Any ideas are welcomed, thanks.
Your viewModel is using the fragment reference. Instead it should use the activity reference.
viewModel = ViewModelProvider(this).get(GP1ViewModel::class.java)
Since you are using fragment reference, it will be destroyed once the fragment is destroyed so data won't be persisted.
Another point to note is if you want you can use a SharedViewModel approach for sharing data across multiple fragment instances.

Need to bind Adapter to RecyclerView twice for data to appear

I have an Android app where I bind a list of service to a RecyclerView as such:
fragment.kt
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
mBinding = FragmentAllServicesBinding.inflate(inflater, container, false)
mViewModel = ViewModelProvider(this).get(AllServicesViewModel::class.java)
binding.viewModel = viewModel
binding.lifecycleOwner = this
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
subscribeServices()
}
// Private Functions
private fun subscribeServices(){
val adapter = ServiceAdapter()
binding.RecyclerViewServices.apply {
/*
* State that layout size will not change for better performance
*/
setHasFixedSize(true)
/* Bind the layout manager */
layoutManager = LinearLayoutManager(requireContext())
this.adapter = adapter
}
viewModel.services.observe(viewLifecycleOwner, { services ->
if(services != null){
lifecycleScope.launch {
adapter.submitList(services)
}
}
})
}
viewmodel.kt
package com.th3pl4gu3.mes.ui.main.all_services
import android.app.Application
import androidx.lifecycle.*
import com.th3pl4gu3.mes.api.ApiRepository
import com.th3pl4gu3.mes.api.Service
import com.th3pl4gu3.mes.ui.utils.extensions.lowercase
import kotlinx.coroutines.launch
import kotlin.collections.ArrayList
class AllServicesViewModel(application: Application) : AndroidViewModel(application) {
// Private Variables
private val mServices = MutableLiveData<List<Service>>()
private val mMessage = MutableLiveData<String>()
private val mLoading = MutableLiveData(true)
private var mSearchQuery = MutableLiveData<String>()
private var mRawServices = ArrayList<Service>()
// Properties
val message: LiveData<String>
get() = mMessage
val loading: LiveData<Boolean>
get() = mLoading
val services: LiveData<List<Service>> = Transformations.switchMap(mSearchQuery) { query ->
if (query.isEmpty()) {
mServices.value = mRawServices
} else {
mServices.value = mRawServices.filter {
it.name.lowercase().contains(query.lowercase()) ||
it.identifier.lowercase().contains(query.lowercase()) ||
it.type.lowercase().contains(query.lowercase())
}
}
mServices
}
init {
loadServices()
}
// Functions
internal fun loadServices() {
// Set loading to true to
// notify the fragment that loading
// has started and to show loading animation
mLoading.value = true
viewModelScope.launch {
//TODO("Ensure connected to internet first")
val response = ApiRepository.getInstance().getServices()
if (response.success) {
// Bind raw services
mRawServices = ArrayList(response.services)
// Set the default search string
mSearchQuery.value = ""
} else {
mMessage.value = response.message
}
}.invokeOnCompletion {
// Set loading to false to
// notify the fragment that loading
// has completed and to hide loading animation
mLoading.value = false
}
}
internal fun search(query: String) {
mSearchQuery.value = query
}
}
ServiceAdapter.kt
class ServiceAdapter : ListAdapter<Service, ServiceViewHolder>(
diffCallback
) {
companion object {
private val diffCallback = object : DiffUtil.ItemCallback<Service>() {
override fun areItemsTheSame(oldItem: Service, newItem: Service): Boolean {
return oldItem.identifier == newItem.identifier
}
override fun areContentsTheSame(oldItem: Service, newItem: Service): Boolean {
return oldItem == newItem
}
}
}
override fun onBindViewHolder(holder: ServiceViewHolder, position: Int) {
holder.bind(
getItem(position)
)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ServiceViewHolder {
return ServiceViewHolder.from(
parent
)
}
}
ServiceViewHolder.kt
class ServiceViewHolder private constructor(val binding: CustomRecyclerviewServiceBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(
service: Service?
) {
binding.service = service
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): ServiceViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding =
CustomRecyclerviewServiceBinding.inflate(layoutInflater, parent, false)
return ServiceViewHolder(
binding
)
}
}
}
The problem here is that, the data won't show on the screen.
For some reasons, if i change my fragment's code to this:
viewModel.services.observe(viewLifecycleOwner, { services ->
if(services != null){
lifecycleScope.launch {
adapter.submitList(services)
// Add this code
binding.RecyclerViewServices.adapter = adapter
}
}
})
Then the data shows up on the screen.
Does anyone have any idea why I need to set the adapter twice for this to work ?
I have another app where I didn't have to set it twice, and it worked. For some reason, this app is not working. (The only difference between the other app and this one is that this one fetches the data from an API whereas the other one fetches data from Room (SQLite) database)
Inside
binding.RecyclerViewServices.apply {
...
}
Change this.adapter = adapter to this.adapter = this#YourFragmentName.adapter
The reason is, you named your Adapter variable "adapter" which conflicts the property name of RecyclerView.adapter. You are actually not setting the adapter for the first time. It's very sneaky, because lint doesn't give any warning and code compiles with no errors...
Or you could rename your "adapter" variable in your fragment to something like "servicesAdapter" an shortly use
binding.RecyclerViewServices.apply {
adapter = servicesAdapter
}
Instead of adding the adapter again try calling adapter.notifyDataSetChanged() after adapter.submitList(services)

Best practice using ViewPager2 with RecyclerView in it and using Navigvation Component for onItemClick of RecyclerView

i am working with single activity and MVVM artitechture its have main activity for just launcher then there is the MainFragment from there we go to ViewPagerContainerFragment using navigation component and in that fragment i have viewpager2 setup with tablayout and in each fragment of viewpager2 there is recyclerview which need to load data from firebase so every thing working fine but the problem is when i click an item in recyclerview this open another fragment for detail info but then i come back i need to load all data back which look a little wired and some times recycler view positions chnaged so there is a way to retain viepager2 state if come back from detail fragment see code below for detail
ViewPager2Adapter.kt
class ViewPager2Adapter(viewPagerContainerFragment : ViewPagerContainerFragment)
: FragmentStateAdapter(viewPagerContainerFragment) {
override fun getItemCount() = 8
override fun createFragment(position: Int): Fragment = ItemsViewPagerFragment()
}
ItemsViewPagerFragment.kt
class ItemsViewPagerFragment : Fragment(), ItemsViewPagerFragmentListener{
private val itemsViewPagerFragmentViewModel: ItemsViewPagerFragmentViewModel by viewModels()
private lateinit var itemsAdapter: ItemsAdapter
private val refreshing by lazy { itemsViewPagerFragmentViewModel.refreshing }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.itemsviewpager_fragment, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initSwipeRefreshLayout()
}
private fun initSwipeRefreshLayout() {
refreshing.value = false
refreshing.observe(viewLifecycleOwner) {
swipeRefreshLayout.isRefreshing = it
}
swipeRefreshLayout.setOnRefreshListener {
refreshData()
}
}
override fun onResume() {
super.onResume()
getData()
}
private fun getData() {
if (!::itemsAdapter.isInitialized) {
refreshing.value = true
initItemsAdapter()
}
showRecyclerView()
}
private fun initItemsAdapter() {
itemsViewPagerFragmentViewModel.getItemsLiveData()
itemsAdapter = ItemsAdapter(
emptyList(),
ItemsClickListener()
)
}
private fun refreshData() {
if (!::itemsAdapter .isInitialized) initItemsAdapter()
else itemsViewPagerFragmentViewModel.getItemsLiveData()
}
private fun showRecyclerView() {
if (isAdded) {
recyclerView.apply {
setHasFixedSize(true)
layoutManager =
if (DeviceOrientationUtil.isLandscape(requireContext()))
StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
else LinearLayoutManager(context)
adapter = itemsAdapter
viewPagerFragmentViewModel.itemsLiveData.observe(viewLifecycleOwner) {
itemsAdapter.updateData(it)
refreshing.value = false
}
}
}
}
}
private inner class ItemsClickListener : ItemsViewHolder.ItemsClickListener {
override fun onItemClick(item: Item) {
findNavController().navigate(R.id.action_itemsViewPagerFragment_to_itemDetailFragment)
}
}
}
ItemsViewPagerFragmentViewModel.kt
class ItemsViewPagerFragmentViewModel : ViewModel() {
private val itemsRepository = ItemsRepository()
val refreshing = MutableLiveData<Boolean>()
lateinit var itemsLiveData: ItemsLiveData
var isDataCached = false
fun getItemsLiveData() {
itemsLiveData = itemsRepository.getitemsLiveData()
}
}

Fragment stucks when, in OnCreateView, a Bitmap (path from SharedPreferences) is set by onBindViewHolder

I will try to be more specific about my issue. I start saying I've already seen this question that shows the same kind of problem, I guess, but I didn't really understand the right solution.
Note: I have created the drawer activity and all navigation dependencies from a template in creating new project step
Initially, I found that when, in ShowProfileFragment, I tried to update the header of the navigation drawer, the same issue happened, so when I put that if statement (see code below) to update it only if necessary, the problem seemed to be vanished (because opening this fragment from drawer doesn't perform this action anymore).
Now, when in ItemListFragment I add all my objects, taken from SharedPreferences to the ArrayList passed to an adapter (ItemCardsAdapter) everything works well until in onBindViewHolder of my adapter I perform setImageBitmap on the ImageView. It appears to be wasting time loading bitmaps...
Note: when I run the app on a real device (my phone) the issue is very highlighted, unlike what happens in the emulator on my pc (here, it was highlighted at the beginning, when issue regarded the profile picture's updating on the drawer's header)
Here is my code:
MainActivity.kt
import ...
class MainActivity : AppCompatActivity() {
private lateinit var appBarConfiguration: AppBarConfiguration
private var host: NavHostFragment? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val toolbar: Toolbar = findViewById(R.id.toolbar)
setSupportActionBar(toolbar)
host = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment?
val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
val navView: NavigationView = findViewById(R.id.nav_view)
var navController = host?.navController //findNavController(R.id.nav_host_fragment)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
// if (savedInstanceState != null) {
// //Restore the fragment's instance
// navController = (supportFragmentManager.getFragment(savedInstanceState, "myFragmentEdit")!! as NavHostFragment?)!!.navController
// }
appBarConfiguration = AppBarConfiguration(setOf(R.id.nav_showprofile, R.id.nav_itemlist), drawerLayout)
setupActionBarWithNavController(navController!!, appBarConfiguration)
navView.setupWithNavController(navController)
/*short info in drawer's header taken from SharedPrefs*/
val sharedPref = getSharedPreferences(getString(R.string.shared_pref_key), Context.MODE_PRIVATE)
val parsedData = sharedPref.getString(getString(R.string.profile_json),"missing data")
if(parsedData != "missing data")
{
val json = Json(JsonConfiguration.Stable)
val obj = json.parse(User.serializer(),parsedData!!)
if(obj.photoPath != "")
navView.getHeaderView(0).header_userpic.setImageBitmap(getCircledBitmap(decodeSampledBitmapFromResource(obj.photoPath,256,256)))
navView.getHeaderView(0).header_username.text = obj.name
navView.getHeaderView(0).header_usermail.text = obj.email
}
else
{
//profile pic path as for as other info are taken from resources here
navView.getHeaderView(0).header_username.text = getString(R.string.user_name)
navView.getHeaderView(0).header_usermail.text = getString(R.string.user_mail)
}
}
// override fun onSaveInstanceState(outState: Bundle) {
// super.onSaveInstanceState(outState)
// supportFragmentManager.putFragment(outState, "myFragmentEdit", host!!)
// }
override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.nav_host_fragment)
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
override fun onBackPressed() {
super.onBackPressed()
}
}
ShowProfileFragment.kt
import ...
class ShowProfileFragment : Fragment() {
companion object {
fun newInstance() = ShowProfileFragment()
}
private lateinit var viewModel: ShowProfileViewModel
#SuppressLint("RestrictedApi")
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val root = inflater.inflate(R.layout.show_profile_fragment, container, false)
viewModel = ViewModelProviders.of(this).get(ShowProfileViewModel::class.java)
/*get User info from SharedPrefs*/
val sharedPref = this.activity!!.getSharedPreferences(getString(R.string.shared_pref_key), Context.MODE_PRIVATE)
val parsedData = sharedPref.getString(getString(R.string.profile_json),"missing data")
if(parsedData != "missing data")
{
val json = Json(JsonConfiguration.Stable)
val obj = json.parse(User.serializer(),parsedData!!)
/*update header info after user clicks save*/
val header = activity!!.findViewById(R.id.nav_view) as NavigationView
header.getHeaderView(0).header_username.text = obj.name
header.getHeaderView(0).header_usermail.text = obj.email
if(obj.photoPath != "")
{
if(arguments?.getString("headerPic") == "update") /*I mean this statement*/
header.getHeaderView(0).header_userpic.setImageBitmap(getCircledBitmap(decodeSampledBitmapFromResource(obj.photoPath,256,256)))
root.photo.setImageBitmap(getCircledBitmap(decodeSampledBitmapFromResource(obj.photoPath,256,256)))
}
viewModel.userKey = obj.userId
root.full_name.text = obj.name
root.nickname.text = obj.nickname
root.email.text = obj.email
root.geographic_area.text = obj.country
//Log.d("kkk","header info from showProfile: name = ${header.getHeaderView(0).header_username.text}")
}
val storageDir = activity!!.getExternalFilesDir(Environment.DIRECTORY_PICTURES)!!
removeIllegalProfilePic(storageDir,sharedPref)
setHasOptionsMenu(true)
return root
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.edit_profile, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when(item.itemId){
R.id.editProfileFragment -> {
val bundle = bundleOf("userKey" to viewModel.userKey)
findNavController().navigate(R.id.action_nav_showprofile_to_editProfileFragment, bundle)
true
}
else -> {
super.onOptionsItemSelected(item)
}
}
//return NavigationUI.onNavDestinationSelected(item, view!!.findNavController()) || super.onOptionsItemSelected(item)
}
#SuppressLint("SimpleDateFormat")
private fun removeIllegalProfilePic(root: File, sPrefs: SharedPreferences){
/*check for illegal 0B file, caused of app termination from the camera activity*/
/*and for illegal file generated by any Activity.RESULT_OK caused of app termination from EditProfileActivity*/
val illegalProfilePic = sPrefs.getString(getString(R.string.illegal_profile_pic_key),"saved")
for(f in root.listFiles()!!)
if (f.length() <= 0 || f.absolutePath == illegalProfilePic)
f.delete()
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(ShowProfileViewModel::class.java)
// TODO: Use the ViewModel
}
}
ItemListFragment.kt
import ...
class ItemListFragment : Fragment() {
companion object {
fun newInstance() = ItemListFragment()
}
private lateinit var viewModel: ItemListViewModel
//var itemCardsArray= ArrayList<Item>()
lateinit var mAdapter : ItemCardsAdapter
#SuppressLint("RestrictedApi")
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
viewModel = ViewModelProviders.of(this).get(ItemListViewModel::class.java)
val root = inflater.inflate(R.layout.item_list_fragment, container, false)
val viewManagerPortrait = LinearLayoutManager(activity)
val viewManagerLandscape = GridLayoutManager(activity, 3)
/*adapter -> taken from SharedPrefs*/
var itemCardsArray= ArrayList<Item>()
//itemCardsArray.add( Item("path","Teddy Bear","Sweet Bear baby peluche","13.49","Toddler Toys","Turin","20/12/2020") )
val sharedPref = this.activity!!.getSharedPreferences(getString(R.string.shared_pref_key), Context.MODE_PRIVATE)
val itemCount = sharedPref.getInt(getString(R.string.item_count), 0)
if(itemCount == 0)
root.listItems.visibility = View.GONE
else
{
root.emptyAds.visibility = View.GONE
mAdapter = ItemCardsAdapter(itemCardsArray, this)
//viewAdapter.notifyDataSetChanged()
root.listItems.apply {
setHasFixedSize(true)
// use a linear layout manager if portrait, grid one else
layoutManager = if(activity!!.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE)
viewManagerLandscape
else
viewManagerPortrait
// specify an viewAdapter (see also next example)
adapter = mAdapter
//adapter!!.notifyDataSetChanged()
}
for(i in 1..itemCount)
{
val parsedData = sharedPref.getString(getString(R.string.item_json)+i.toString(),"missing data")
if(parsedData != "missing data")
{
val json = Json(JsonConfiguration.Stable)
val obj = json.parse(Item.serializer(),parsedData!!)
itemCardsArray.add(i-1, obj)
mAdapter.notifyItemInserted(i-1)
}
}
}
root.fab_addItem.setOnClickListener {
Snackbar.make(it, "Creating new advertisement...", Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
findNavController().navigate(R.id.action_nav_itemlist_to_itemEditFragment)
}
return root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(ItemListViewModel::class.java)
// TODO: Use the ViewModel
}
}
ItemCardsAdapter.kt
import ...
class ItemCardsAdapter(private val myDataset: ArrayList<Item>, private val f: Fragment) : RecyclerView.Adapter<ItemCardsAdapter.MyViewHolder>() {
class MyViewHolder(viewItem: View) : RecyclerView.ViewHolder(viewItem){
val itemphoto = viewItem.itemphoto
val itemtitle = viewItem.title
val itemprice = viewItem.itemprice
val itemlocation = viewItem.itemlocation
val editpencil = viewItem.editCard
}
// Create new views (invoked by the layout manager)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
// create a new view
val viewItem = LayoutInflater.from(parent.context).inflate(R.layout.item_card, parent, false)
// set the view's size, margins, paddings and layout parameters
return MyViewHolder(viewItem)
}
// Replace the contents of a view (invoked by the layout manager)
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.itemtitle.text = myDataset[position].title
holder.itemprice.text = myDataset[position].price
holder.itemlocation.text = myDataset[position].location
if(myDataset[position].photoPath != "") /*Here it stucks on load !?*/
holder.itemphoto.setImageBitmap(decodeSampledBitmapFromResource(myDataset[position].photoPath,256,256))
//holder.View.startAnimation(AnimationUtils.loadLayoutAnimation(this,R.anim.layout_animation))
val bundle = bundleOf("itemAdKey" to myDataset[position].adId)
holder.editpencil.setOnClickListener {
bundle.putString("nav", "editCard")
f.findNavController().navigate(R.id.action_nav_itemlist_to_itemEditFragment, bundle)
}
holder.itemView.setOnClickListener {
f.findNavController().navigate(R.id.action_nav_itemlist_to_nav_itemdetails, bundle)
}
}
// Return the size of your dataset (invoked by the layout manager)
override fun getItemCount() = myDataset.size
}
I found the solution, following this official documentation:
https://developer.android.com/topic/performance/graphics
that recommends to use this external library to solve that kind of memory caching issues: Glide
So, I changed, for example, this
ItemCardsAdapter.kt
holder.itemphoto.setImageBitmap(decodeSampledBitmapFromResource(myDataset[position].photoPath,256,256))
with this
Glide.with(f.activity!!).load(myDataset[position].photoPath).into(holder.itemphoto)

Categories

Resources