Why ViewModel is still alive after I replaced fragment in Android? - android-bottomnavigationview

I use BottomNavigation in Android,But,After I replace RotationFragment to ScaleFragment,Back to BottomNavigationFragment,RotationViewModel's rotation value always alive
MainActivity
class MainActivity : AppCompatActivity() {
lateinit var binding : ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val navHostFragment = supportFragmentManager
.findFragmentById(binding.navHostFragment.id) as NavHostFragment
val navController = navHostFragment.navController
val configuration = AppBarConfiguration.Builder(binding.bottomNavigationView.menu).build()
NavigationUI.setupActionBarWithNavController(this, navController,configuration)
NavigationUI.setupWithNavController(binding.bottomNavigationView, navController)
}
}
RotationFragment
When image clicking,after rotation value add 100,use animator rotation image and save (rotation+100) to ViewModel rotation value,but,after switch ScaleFragment,back to RotationFragment RotationViewModel's rotation is 100 not 0
class RotationFragment : Fragment() {
private lateinit var viewBinding : RotationFragmentBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
viewBinding = RotationFragmentBinding.inflate(inflater, container, false)
return viewBinding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val viewModel = ViewModelProvider(this).get(RotationViewModel::class.java)
//viewBinding.imgRotationMood.rotation = viewModel.rotation.toFloat()
Log.d("onViewCreated", "onViewCreated: ${viewModel.rotation.toFloat()}")
val animatorMood = ObjectAnimator.ofFloat(viewBinding.imgRotationMood, "rotation", 0.0f,0.0f)
animatorMood.duration = 500
viewBinding.imgRotationMood.setOnClickListener {
if(!animatorMood.isRunning) {
animatorMood.setFloatValues(viewBinding.imgRotationMood.rotation,
viewBinding.imgRotationMood.rotation + 100)
viewModel.rotation = viewModel.rotation + 100
animatorMood.start()
}
}
}
}
RotationViewModel
class RotationViewModel : ViewModel() {
var rotation = 0
}
I trying use official BottomNavigation Template,get same result

Related

How to navigate from item in recylerview to new fragment

Can someone help me figure out how i am supposed to navigate from an item in recyclerview to this repoFragment? I want to display these things in repoFragment using the GitHub API.
I want to click the reponame in the item of the recycler view. With the onClickListener i wish to open a new fragment (RepoFragment) with the info of the clicked item from the recyclerview.
I'm doing a repository search and want to display the info when clicking on the repository.The code below represents what i tried to do.
Thank you for your help!
This is my RepositoryRecyclerAdapter:
class RepositoryRecyclerAdapter(repos: List<RepositoriesResponse>): RecyclerView.Adapter<SearchPlaceHolder>(){
private var repoList = repos
var navc: NavController?= null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchPlaceHolder {
val inflatedView = LayoutInflater.from(parent.getContext()).inflate(R.layout.pravokutnik, parent, false);
return SearchPlaceHolder(inflatedView)
}
override fun onBindViewHolder(holder: SearchPlaceHolder, position: Int) {
val repo = repoList[position]
holder.bindRepo(repo)
holder.itemView.setOnClickListener(object: View.OnClickListener{
override fun onClick(v: View){
navc?.navigate(R.id.action_searchFragment_to_repoFragment)
}
})
}
override fun getItemCount() = repoList.size
}
This is my SearchFragment:
class SearchFragment : BaseFragment<SearchViewModel, FragmentSearchBinding, SearchRepository>() {
private lateinit var mContext: Context
private var reposList = ArrayList<RepositoriesResponse>().toList()
private lateinit var adapterRepo: RepositoryRecyclerAdapter
private lateinit var linearLayoutManagerDistance: LinearLayoutManager
val args: SearchFragmentArgs by navArgs()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mContext = this.requireContext()
linearLayoutManagerDistance = LinearLayoutManager(mContext)
linearLayoutManagerDistance.orientation = LinearLayoutManager.VERTICAL
binding.repositoriesList.layoutManager = linearLayoutManagerDistance
val query = args.query
val apiInterface = RepositoryApi.create().getRepositoriesByQuery(query)
apiInterface.enqueue( object : Callback<QueryResponse> {
override fun onResponse(call: Call<QueryResponse>?, response: Response<QueryResponse>?) {
if(response?.body() != null){
reposList = response.body()!!.items
adapterRepo = RepositoryRecyclerAdapter(reposList)
binding.repositoriesList.adapter = adapterRepo
}
}
override fun onFailure(call: Call<QueryResponse>?, t: Throwable?) {
Toast.makeText(mContext, t.toString(), Toast.LENGTH_LONG).show()
}
})
}
override fun getViewModel() = SearchViewModel::class.java
override fun getFragmentBinding(
inflater: LayoutInflater,
container: ViewGroup?
) = FragmentSearchBinding.inflate(inflater, container, false)
override fun getFragmentRepository()=
SearchRepository()
}
The HomeFragment:
class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding, SearchRepository>() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_home, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
var mySearchView = view.findViewById<SearchView>(R.id.search_view)
var navc = Navigation.findNavController(view)
mySearchView.setIconifiedByDefault(true);
mySearchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
val action = HomeFragmentDirections.actionHomeFragmentToSearchFragment2(query)
navc?.navigate(action)
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
return false
}
})
}
override fun getViewModel() = HomeViewModel::class.java
override fun getFragmentBinding(
inflater: LayoutInflater,
container: ViewGroup?
) = FragmentHomeBinding.inflate(inflater, container, false)
override fun getFragmentRepository()=
SearchRepository()
}
RepoFragment:
class RepoFragment : BaseFragment<RepoViewModel, FragmentRepoBinding, SearchRepository>(){
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_repo, container, false)
}
override fun getViewModel() = RepoViewModel::class.java
override fun getFragmentBinding(
inflater: LayoutInflater,
container: ViewGroup?
) = FragmentRepoBinding.inflate(inflater, container, false)
override fun getFragmentRepository() = SearchRepository()
}
Also, I get an error when trying to run this code; androidstudio says that i made a mistake when inflating the layout of repofragment.

Can i monitor a variable changed in another class in kotlin?

Im in a fragment1 and i want go to fragment2 if an event occurred in a class called from the fragment1. I have tried a callback of fuction: function in Class call a function in fragment1 to go in fragment but i collect this error:
Process: com.example.ilmiogioco, PID: 7992java.lang.IllegalStateException: Method addObserver must be called on the main thread
at androidx.lifecycle.LifecycleRegistry.enforceMainThreadIfNeeded(LifecycleRegistry.java:317)
at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.java:172)
at androidx.savedstate.SavedStateRegistryController.performRestore(SavedStateRegistryController.java:61)
at androidx.navigation.NavBackStackEntry.<init>(NavBackStackEntry.java:88)
at androidx.navigation.NavBackStackEntry.<init>(NavBackStackEntry.java:73)
at androidx.navigation.NavController.navigate(NavController.java:1138)
at androidx.navigation.NavController.navigate(NavController.java:944)
at androidx.navigation.NavController.navigate(NavController.java:877)
at androidx.navigation.NavController.navigate(NavController.java:863)
at androidx.navigation.NavController.navigate(NavController.java:851)
at com.example.ilmiogioco.FullscreenFragmentSolo.follow(FullscreenFragmentSolo.kt:77)
at com.example.ilmiogioco.Solo.SpaceView.update(SpaceView.kt:276)
at com.example.ilmiogioco.Solo.SpaceView.run(SpaceView.kt:120)
at java.lang.Thread.run(Thread.java:919)
EDIT: I have fullscreenfragmentsolo (fragment1) that want in gameoverfragment (fragment2) if the class spaceview called in fullscreenfragmentsolo collect a lost game. The function follow() is called by spaceview for return in fullscreenfragmentsolo (maybe this is the thread error).
class FullscreenFragmentSolo : Fragment() {
private var spaceView: SpaceView? = null
private lateinit var backgroundMusic: MediaPlayer
private lateinit var window: Window
private var binding: FragmentFullscreenSoloBinding? = null
object size{
var x = 0
var y = 0
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
(activity as AppCompatActivity?)!!.supportActionBar!!.hide()
getActivity()?.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
val soundEffects = SoundEffects(requireContext())
soundEffects.playSound(SoundEffects.backgroundMusic)
val outMetrics = DisplayMetrics()
getActivity()?.getWindow()?.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
window = activity?.getWindow()!!
window.attributes.width
window.attributes.height
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
val display = activity?.display
display?.getRealMetrics(outMetrics)
} else {
#Suppress("DEPRECATION")
val display = activity?.windowManager?.defaultDisplay
#Suppress("DEPRECATION")
display?.getMetrics(outMetrics)
}
size.y = outMetrics.heightPixels
size.x = outMetrics.widthPixels
backgroundMusic = MediaPlayer.create(requireContext(), R.raw.background_music)
backgroundMusic.isLooping = true
backgroundMusic.start()
val fragmentBinding = FragmentFullscreenSoloBinding.inflate(inflater, container, false)
binding = fragmentBinding
fragmentBinding.root
spaceView = SpaceView(requireContext(), size, this)
return spaceView
}
fun follow(){
findNavController().navigate(R.id.action_fullscreenFragmentSolo_to_gameoverFragment)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.soloFragment = this
}
override fun onResume() {
super.onResume()
spaceView?.resume()
}
fun stopFunction() {
spaceView?.stop()
}
override fun onPause() {
super.onPause()
backgroundMusic.release()
spaceView?.pause()
}
override fun onDestroyView() {
super.onDestroyView()
binding = null
}
GameoverFragment:
open class GameoverFragment : Fragment() {
private var binding: GameoverFragmentBinding? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val fragmentBinding = GameoverFragmentBinding.inflate(inflater, container, false)
binding = fragmentBinding
return fragmentBinding.root
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.gameoverFragment = this
}
fun Menu(){
findNavController().navigate(R.id.action_gameoverFragment_to_startFragment)
}
> override fun onDestroyView() {
> super.onDestroyView()
> binding = null }
Can you help me?
This exception is due to your code calling (through navigation) the LifecycleRegistry.addObserver from a thread other than the Main thread. You have to ensure that you call the navigation from the main thread.
Change to this in the follow() function
import android.os.Handler
import android.os.Looper
// ...
fun follow() {
Handler(Looper.getMainLooper()).post {
findNavController().navigate(R.id.action_fullscreenFragmentSolo_to_gameoverFragment)
}
}

How to solve view binding null pointer exception when activity restarts and fragment recreates

Anytime my MainActivity restarts because of changing of dark mode and i try to click i get nullpointer exception.
The error is below
I have a BaseFragment
lateinit var mContext:Context
lateinit var mActivity:Activity
var myId = ""
val firebaseMethods: FirebaseMethods by lazy { FirebaseMethods(requireActivity()) }
override fun onDestroy() {
super.onDestroy()
removeListeners()
}
override fun onAttach(context: Context) {
super.onAttach(context)
showLogI("onAttach")
}
override fun onDetach() {
super.onDetach()
showLogI("onDetach")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mContext = requireContext()
mActivity = requireActivity()
showLogI("onCreate")
if(getCurrentUser() != null){
myId = getCurrentUserId()
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?)
: View {
showLogI("onCreateView")
val view = setContentView(inflater, container, savedInstanceState)
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initView(view)
setListener()
initData()
}
protected abstract fun setContentView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?):View
protected abstract fun setListener()
protected abstract fun initData()
I also have a PostFragment
var posts:ArrayList<Post> = arrayListOf()
var pgPosts:ArrayList<Post> = arrayListOf()
var ids:ArrayList<String> = arrayListOf()
val keys:ArrayList<String> = arrayListOf()
var totalSize = 0
var query: Query? = null
var postAdapter:PostAdapter? = null
var counter = 0
var adUnit = ""
var adminId = ""
override fun onDestroy() {
super.onDestroy()
postAdapter?.removeListeners()
destroyAds()
}
private var _binding: FragmentPostBinding? = null
private val binding get() = _binding!!
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun setContentView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentPostBinding.inflate(inflater, container, false)
return binding.root
}
override fun setListener() {
}
override fun initData() {
getArgs()
toggleOrientation()
showProgress()
setAdapter()
val spannableString = "No Internet Connection. Retry".createSpan("Retry"){ spannableString, start, end ->
mContext.setSpan(spannableString, "click", start, end, R.color.green){readPosts()}
}
if (mContext.isConnectingToInternet()) readPosts() else hideProgressWithSpan(spannableString)
adUnit = mContext.getString(R.string.ads_unit_video)
firebaseMethods.getAdmin { admin->
adminId = admin.admin_id
//adUnit = admin.native_ads_unit
}
}
fun changeLayout(orientation:String){
this.orientation = orientation
toggleOrientation()
setAdapter()
}
fun toggleOrientation(){
if (orientation == "big") {
max = 5
binding.recyclerview.lm()
} else if (orientation == "small") {
max = 15
binding.recyclerview.lm("grid", 3)
}
}
I get this null pointer exception
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.wh, PID: 26862
java.lang.NullPointerException at com.wh.ui.fragment.post.PostFragment.getBinding(PostFragment.kt:101)
at com.wh.ui.fragment.post.PostFragment.toggleOrientation(PostFragment.kt:160)
at com.wh.ui.fragment.post.PostFragment.changeLayout(PostFragment.kt:154)
As #Tenfour04 has said , you're accessing the binding when the fragment has been detached. You need to first check if the fragment has been added like so
if(isAdded){
//do something with the binding
}
Thanks a lot. I finally found the solution. I was checking if state is null for my bottom navigation view fragments and if null using new fragment, if not finding fragmentbytag. I just guessed to create the fragment without checking state and it worked.

Kotlin RecyclerView - Update item to DB

I have a category item in my recyclerView.
There is a TextView and two ImageView as button (Edit button and Delete button).
When I click edit button I want to change TextView to EditText and editbutton change for agreebutton. When I write new text just update my old one.
I show you what I have and almost everything working but don't update my new text and I know code don't look nice and maybe someone can show me how to do it better :)
class CategoryAdapter(private val categoryList: List<Category>, val listener: ClickListener) : RecyclerView.Adapter<CategoryAdapter.MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(CategoryItemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.binding.categoryName.text = categoryList[position].name
holder.binding.categoryEditIv.setOnClickListener {
holder.binding.categoryName.visibility = View.GONE
holder.binding.categoryEditName.visibility = View.VISIBLE
holder.binding.categoryEditName.setText(categoryList[position].name)
holder.binding.categoryEditIv.visibility = View.GONE
holder.binding.categoryAgreeIv.visibility = View.VISIBLE
}
val editTxt = holder.binding.categoryEditName
holder.binding.categoryAgreeIv.setOnClickListener {
val edit = editTxt.text.toString()
listener.editAgreeClickItem(edit)
holder.binding.categoryName.visibility = View.VISIBLE
holder.binding.categoryEditName.visibility = View.GONE
holder.binding.categoryAgreeIv.visibility = View.GONE
holder.binding.categoryEditIv.visibility = View.VISIBLE
}
holder.binding.categoryDeleteIv.setOnClickListener {
listener.deleteClickItem(categoryList[position])
}
}
override fun getItemCount(): Int {
return categoryList.size
}
class MyViewHolder(val binding: CategoryItemBinding) : RecyclerView.ViewHolder(binding.root)
interface ClickListener {
fun editAgreeClickItem(text: String)
fun deleteClickItem(category: Category)
}
}
class MainFragment : Fragment(), NewCategory.NewCategoryCreateListener, CategoryAdapter.ClickListener {
private var _binding: FragmentMainBinding? = null
private val binding
get() = _binding!!
private lateinit var shoppingListViewModel: ShoppingListViewModel
private lateinit var categoryAdapter: CategoryAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
shoppingListViewModel = ViewModelProvider(requireActivity())[ShoppingListViewModel::class.java]
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentMainBinding.inflate(layoutInflater,container,false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.categoryRV.layoutManager = LinearLayoutManager(requireContext())
shoppingListViewModel.allCategories.observe(viewLifecycleOwner, {
updateCategories(it)
})
binding.addCategory.setOnClickListener {
val newCategory = NewCategory(this)
newCategory.show(childFragmentManager, "NewCategory")
}
}
private fun updateCategories(list: List<Category>) {
if(list.size == 0){
binding.noResult.visibility = View.VISIBLE
binding.categoryRV.visibility = View.GONE
}else{
binding.noResult.visibility = View.GONE
binding.categoryRV.visibility = View.VISIBLE
categoryAdapter = CategoryAdapter(list, this)
binding.categoryRV.adapter = categoryAdapter
}
}
override fun newCategoryCreate(text: String) {
val newCat = Category(text)
shoppingListViewModel.insertCategory(newCat)
}
override fun editAgreeClickItem(text: String) {
val newText = Category(text)
shoppingListViewModel.updateCategory(newText)
}
override fun deleteClickItem(category: Category) {
shoppingListViewModel.deleteCategory(category)
}
}

Problems with SmartToolbar not recreating fragments

I'm having a problem with this SmartToolbar, if I open the app and click on the chat or book icon it opens perfectly and shows the fragments, but if I click on another icon to open another fragment, when I go back to them they don't show more The fragments that should be on the smartToolbar, as shown in the prints below, bug everything! I need to help on this as soon as possible !!!
[Opens normally When I click on another item and go back to it, it no longer opens the SmartToolbar fragmentshere]2
enter image description here
I will send the homeAcitivity code, where is the layout that opens the fragments of the buttonNavigationBar
class HomeActivity : AppCompatActivity() {
var MY_ACCOUNT = 1
var LIBRARY = 2
var HOME = 3
var CHAT = 4
var SETTINGS = 5
private var Content: ConstraintLayout? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
supportActionBar!!.hide()
nav.show(HOME)
nav.setBackgroundColor(Color.TRANSPARENT)
abrirHome()
addOnMeowNavigattionBarIcons()
addOnNotificationInform()
chamarFragments()
}
fun abrirHome() {
val fragment = HomeFragment.newInstance()
addFragment(fragment)
}
fun addOnMeowNavigattionBarIcons() {
nav.add(MeowBottomNavigation.Model(MY_ACCOUNT, R.drawable.ic_baseline_account_circle_24))
nav.add(MeowBottomNavigation.Model(LIBRARY, R.drawable.ic_baseline_menu_book_24))
nav.add(MeowBottomNavigation.Model(HOME, R.drawable.ic_baseline_home_24))
nav.add(MeowBottomNavigation.Model(CHAT, R.drawable.ic_baseline_chat_24))
nav.add(MeowBottomNavigation.Model(SETTINGS, R.drawable.ic_baseline_settings_24))
}
fun addOnNotificationInform() {
nav.setCount(CHAT, "99")
}
fun chamarFragments() {
nav.setOnClickMenuListener {
when (it.id) {
HOME -> {
val fragment = HomeFragment.newInstance()
addFragment(fragment)
}
MY_ACCOUNT -> {
val fragment = MyAccountFragment.newInstance()
addFragment(fragment)
}
LIBRARY -> {
val fragment = LibraryFragment.newInstance()
addFragment(fragment)
}
CHAT -> {
val fragment = ChatFragment.newInstance()
addFragment(fragment)
}
SETTINGS -> {
val fragment = SettingsFragment.newInstance()
addFragment(fragment)
}
}
}
}
private fun addFragment(fragment: Fragment) {
supportFragmentManager
.beginTransaction()
.replace(R.id.parentfragments,fragment,fragment.javaClass.simpleName)
.addToBackStack(null)
.commit()
}
fun sair(view: View){
FirebaseAuth.getInstance().signOut()
val bundle = ActivityOptions.makeSceneTransitionAnimation(this).toBundle()
val intent = Intent(this, login::class.java)
startActivity(intent,bundle)
}
}
This is the code of the fragment Library that has a SmartToolbar that calls two more fragments within it, MyLibrary and Store
class LibraryFragment: Fragment() {
companion object{
fun newInstance():LibraryFragment {
val args = Bundle()
val fragment = LibraryFragment()
fragment.arguments = args
return fragment
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.library_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val adapter = FragmentPagerItemAdapter(
fragmentManager, FragmentPagerItems.with(context)
.add("Minha Bilioteca", MyLibraryFragment::class.java)
.add("Loja", StoreFragment::class.java)
.create())
viewpager.adapter = adapter
viewpagertab.setViewPager(viewpager)
}
}
And this is the MyLibrary fragment that is shown inside the Library that is shown inside HomeActivity
class MyLibraryFragment: Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.mylibrary_fragment, container, false)
}
}

Resources