BottomSheetDialogFragment shows a memory leak, in LeakCanary 2, but I'm not sure why? - memory-leaks

I've been looking through a number of posts and I have not found anything to help me understand why LeakCanary is reporting a leak. I have a main activity with a com.google.android.material.bottomappbar.BottomAppBar and that shows a BottomSheetDialogFragment. When you select an item in the bottom sheet it updates some text then dismisses the dialog fragment. Now running this with LeakCanary shows a leak when the dialog is presented and an item is selected.
The leak looks like:
====================================
HEAP ANALYSIS RESULT
====================================
1 APPLICATION LEAKS
References underlined with "~~~" are likely causes.
Learn more at https://squ.re/leaks.
152090 bytes retained by leaking objects
Signature: 3841703253a9bf9893936b1dd318c9dd54bf5a8
┬───
│ GC Root: System class
│
├─ android.app.ActivityThread class
│ Leaking: NO (MainActivity↓ is not leaking and a class is never leaking)
│ ↓ static ActivityThread.sCurrentActivityThread
├─ android.app.ActivityThread instance
│ Leaking: NO (MainActivity↓ is not leaking)
│ ↓ ActivityThread.mActivities
├─ android.util.ArrayMap instance
│ Leaking: NO (MainActivity↓ is not leaking)
│ ↓ ArrayMap.mArray
├─ java.lang.Object[] array
│ Leaking: NO (MainActivity↓ is not leaking)
│ ↓ Object[].[1]
├─ android.app.ActivityThread$ActivityClientRecord instance
│ Leaking: NO (MainActivity↓ is not leaking)
│ ↓ ActivityThread$ActivityClientRecord.activity
├─ com.example.testleak.MainActivity instance
│ Leaking: NO (Activity#mDestroyed is false)
│ ↓ MainActivity.bottomNavFragment
│ ~~~~~~~~~~~~~~~~~
╰→ com.example.testleak.BottomNavigationDrawerFragment instance
​ Leaking: YES (ObjectWatcher was watching this because com.example.testleak.BottomNavigationDrawerFragment received Fragment#onDestroy() callback and Fragment#mFragmentManager is null)
​ key = 74db5e32-a816-418a-9f83-2f50a05f37a4
​ watchDurationMillis = 7224
​ retainedDurationMillis = 2210
​ key = 92744a08-2122-4fbe-9841-08f805fcf6e5
​ retainedDurationMillis = 2212
====================================
0 LIBRARY LEAKS
Library Leaks are leaks coming from the Android Framework or Google libraries.
====================================
METADATA
Please include this in bug reports and Stack Overflow questions.
Build.VERSION.SDK_INT: 26
Build.MANUFACTURER: motorola
LeakCanary version: 2.1
App process name: com.example.testleak
Analysis duration: 4182 ms
Heap dump file path: /data/user/0/com.example.testleak/files/leakcanary/2020-02-20_10-42-59_515.hprof
Heap dump timestamp: 1582213384806
====================================
So it appears that BottomNavigationDrawerFragment should be cleaning up something in it's onDestroy() method.
Here are the main files involved.
MainActivity.kt
package com.example.testleak
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import com.example.testleak.BottomNavigationDrawerFragment.OnItemClickListener
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.content_main.*
class MainActivity : AppCompatActivity() {
private var bottomNavFragment: BottomNavigationDrawerFragment? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(bottom_app_bar)
bottomNavFragment = BottomNavigationDrawerFragment(clickListener)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
return true
}
private var clickListener = object : OnItemClickListener {
override fun onItemClick(item: Int?) {
// Based on the item clicked show that fragment
bottomNavFragment?.dismiss()
when (item) {
R.id.nav_item_1 -> {
screen_label.text = resources.getString(R.string.item_1)
}
R.id.nav_item_2 -> {
screen_label.text = resources.getString(R.string.item_2)
}
R.id.nav_item_3 -> {
screen_label.text = resources.getString(R.string.item_3)
}
R.id.nav_item_4 -> {
screen_label.text = resources.getString(R.string.item_4)
}
R.id.preferences -> {
screen_label.text = resources.getString(R.string.preferences)
}
}
}
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item!!.itemId) {
android.R.id.home -> {
bottomNavFragment!!.show(supportFragmentManager, bottomNavFragment!!.tag)
}
}
return true
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="#layout/content_main" />
<com.google.android.material.bottomappbar.BottomAppBar
android:id="#+id/bottom_app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:backgroundTint="?attr/colorPrimary"
app:fabAlignmentMode="center"
app:hideOnScroll="true"
app:layout_scrollFlags="scroll|enterAlways"
app:navigationIcon="#drawable/ic_menu_white_24dp"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/fab"
style="#style/Widget.MaterialComponents.FloatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_add_circle_outline_white_24dp"
app:layout_anchor="#id/bottom_app_bar" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
content_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="#+id/coordinatorLayout2"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/screen_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="120dp"
android:text="Primary"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</LinearLayout>
BottomNavigationDrawerFragment.kt
package com.example.testleak
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.navigation.NavigationView
class BottomNavigationDrawerFragment(private val clickListener: OnItemClickListener) : BottomSheetDialogFragment() {
lateinit var mapNavView: NavigationView
interface OnItemClickListener {
fun onItemClick(item: Int?)
}
override fun onDestroy() {
Log.d("BottomNavFragment", "onDestroy")
super.onDestroy()
}
override fun onDestroyView() {
Log.d("BottomNavFragment", "onDestroyView")
super.onDestroyView()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
Log.d("BottomNavFragment", "onCreateView")
val v = inflater.inflate(R.layout.fragment_bottomsheet, container, false)
mapNavView = v.findViewById(R.id.nav_view)
mapNavView.setNavigationItemSelectedListener { menuItem ->
// Bottom Navigation Drawer menu item clicks
clickListener.onItemClick(menuItem.itemId)
true
}
return v
}
}
fragment_bottomsheet.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:fitsSystemWindows="true"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.navigation.NavigationView
android:id="#+id/nav_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:menu="#menu/bottom_nav_drawer_menu" />
</androidx.constraintlayout.widget.ConstraintLayout>
bottom_nav_drawer_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="none">
<item
android:id="#+id/nav_item_1"
android:title="#string/item_1" />
<item
android:id="#+id/nav_item_2"
android:title="#string/item_2" />
<item
android:id="#+id/nav_item_3"
android:title="#string/item_3" />
<item
android:id="#+id/nav_item_4"
android:title="#string/item_4" />
<item
android:id="#+id/preferences"
android:title="#string/preferences" />
</group>
</menu>
Any help would be greatly appreciated. I'm sure there is something I'm overlooking but I'm guessing I've looked at it so long I'm looking past it.
-Rindress

The key part of the leaktrace to look at is where the ~~~ are :
├─ com.example.testleak.MainActivity instance
│ Leaking: NO (Activity#mDestroyed is false)
│ ↓ MainActivity.bottomNavFragment
│ ~~~~~~~~~~~~~~~~~
╰→ com.example.testleak.BottomNavigationDrawerFragment instance
​ Leaking: YES (ObjectWatcher was watching this because
com.example.testleak.BottomNavigationDrawerFragment received Fragment#onDestroy() callback
and Fragment#mFragmentManager is null)
This tells us that MainActivity is not destroyed, but BottomNavigationDrawerFragment is. When BottomNavigationDrawerFragment becomes destroyed, it should be garbage collected. However it cannot be garbage collected because MainActivity is keeping a reference to it in MainActivity.bottomNavFragment
When MainActivity.clickListener calls bottomNavFragment?.dismiss() it should also set bottomNavFragment to null. And instead of setting MainActivity.bottomNavFragment to a new instance in MainActivity.onCreate(), the new instance should be created when the fragment is shown, e.g. in MainActivity.onOptionsItemSelected

Related

How can I pass the checked radio button value into text View in kotlin android studio?

I made a dialogue box and I put radio group in it and I add three radio button 1)male 2)female 3)others if user select male so the selected radio button male should be shown in text View
Here, I would like to share you here a simple app with both the activity and layout code. I hope this will help you.
First the layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:padding="100dp"
tools:context=".TestActivity">
<TextView
android:id="#+id/textView3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="20dp"
android:text="What is your gender?"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:id="#+id/txtViewGender"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="20dp"
android:text="-- Selected Gender --"
android:textSize="16sp" />
<Button
android:id="#+id/button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Select" />
</LinearLayout>
Second the Main Activity code
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
class GenderActivity: AppCompatActivity() {
private lateinit var selectedGender: String
private var selectedGenderIndex: Int = 0
private val gender = arrayOf("Male", "Female", "Others")
private lateinit var txtViewGender: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test4)
txtViewGender = findViewById<TextView>(R.id.txtViewGender)
var androidButton: Button = findViewById<Button>(R.id.button)
androidButton.setOnClickListener {
showRadioDialog()
}
}
private fun showRadioDialog() {
selectedGender = gender[selectedGenderIndex]
MaterialAlertDialogBuilder(this)
.setTitle("Select your gender")
.setSingleChoiceItems(gender, selectedGenderIndex) { dialog, which ->
selectedGenderIndex = which
selectedGender = gender[which]
}
.setPositiveButton("Ok") { dialog, which ->
Toast.makeText(this, "Selected --> $selectedGender ", Toast.LENGTH_SHORT)
.show()
txtViewGender.text = selectedGender
}
.setNegativeButton("Cancel") { dialog, which ->
dialog.dismiss()
}
.show()
}
}
When you run the code, you will get the following two screens.
Screen 1
Screen 2

How to drag and drop ListView rows in Android Studio using Kotlin

I’m using Android Studio with Kotlin. I’d like to be able to drag and drop items in a ListView to reorder them. I also want to swipe left to delete. This was pretty straightforward in XCode/Swift for iOS, but I’m having trouble finding examples for Android/Kotlin. I’ve seen some fairly old discussions with Java and with RecyclerView, but it would be great to be able to do it in Kotlin with ListView. I should add that the ListView does everything I need so far – I realise there is much discussion about ListView vs RecyclerView.
I’m not asking for people to solve this for me, but it would be great if you could point me to anything that might get me going.
By way of background, here is my code. It is just a ListView with numbers ‘zero’ to ‘ten’ and the selected row highlighted. I should add that I wrote this code with the help of many discussions here and on YouTube, for which I’m very grateful.
I also have a ListView with sections that needs these options, but I’ve not put the code here for that (happy to do so if required).
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ListView
android:id="#+id/list_view"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_margin="16dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
listview_row.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/rowText"
android:layout_width="match_parent"
android:layout_height="48dp"
android:textColor="#color/black"
android:paddingLeft="0dp"
android:paddingTop="8dp"
android:paddingRight="0dp"
android:paddingBottom="8dp"
android:autoSizeTextType="uniform"
android:autoSizeMaxTextSize="40sp"
android:autoSizeMinTextSize="8sp"
android:autoSizeStepGranularity="2sp"
android:lines="1" />
</LinearLayout>
listview_selected_row.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/selectedRowText"
android:layout_width="match_parent"
android:layout_height="48dp"
android:paddingLeft="0dp"
android:paddingRight="0dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:background="#color/teal_200"
android:textColor="#color/black"
android:autoSizeTextType="uniform"
android:autoSizeMaxTextSize="40sp"
android:autoSizeMinTextSize="8sp"
android:autoSizeStepGranularity="2sp"
android:lines="1" />
</LinearLayout>
ListViewAdapter.kt
package com.ijmusic.listviewrearrange
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.TextView
val TYPE_LISTVIEW_ITEM = 0
val TYPE_LISTVIEW_SELECTED_ITEM = 1
class ListViewAdapter(private val context: Context): BaseAdapter() {
private val mData: ArrayList<String> = ArrayList()
private var mIndex: ArrayList<Int> = ArrayList()
private var mSelectedItem = -1
private lateinit var textView: TextView
fun addItem(item: String, index: Int) {
mData.add(item)
mIndex.add(index)
notifyDataSetChanged()
}
fun addSelectedItem(item: String, index: Int) {
mData.add(item)
mIndex.add(index)
mSelectedItem = mData.size - 1
notifyDataSetChanged()
}
fun highlightSelection(int: Int) {
mSelectedItem = int
notifyDataSetChanged()
}
override fun getItemViewType(position: Int): Int {
return if (position == mSelectedItem) TYPE_LISTVIEW_SELECTED_ITEM
else TYPE_LISTVIEW_ITEM
}
override fun getCount(): Int {
return mData.size
}
override fun getItem(position: Int): Any {
return mData[position]
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getView(position: Int, view: View?, parent: ViewGroup?): View? {
var view = view
var type = getItemViewType(position)
if (position == mSelectedItem) type = TYPE_LISTVIEW_SELECTED_ITEM
when (type) {
TYPE_LISTVIEW_ITEM -> {
view = LayoutInflater.from(context).inflate(R.layout.listview_row, parent, false)
textView = view.findViewById(R.id.rowText)
}
TYPE_LISTVIEW_SELECTED_ITEM -> {
view = LayoutInflater.from(context).inflate(R.layout.listview_selected_row, parent, false)
textView = view.findViewById(R.id.selectedRowText)
}
}
textView.text = mData[position]
return view
}
}
MainActivity.kt
package com.ijmusic.listviewrearrange
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.AdapterView
import com.ijmusic.listviewrearrange.databinding.ActivityMainBinding
val list = listOf("zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten")
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var mAdapter: ListViewAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
mAdapter = ListViewAdapter(this)
setListView()
binding.listView.onItemClickListener = object: AdapterView.OnItemClickListener {
override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
binding.listView.setSelection(position)
mAdapter.highlightSelection(position)
}
}
}
fun setListView() {
for ( i in 0 until list.count() ) {
if ( i == 0 )
mAdapter.addSelectedItem(list[i], i)
else
mAdapter!!.addItem(list[i], i)
}
binding.listView.adapter = mAdapter
}
}
You can look into a concept called gestures in android Kotlin. It will help.

W/RecyclerView: No adapter attached; skipping layout

I have made a basic shopping list app that utilises a recyclerview to display the list items. I am trying to add a settings screen using navigation with fragments. I am running into the issue where my recyclerview & data display when I open the app, however when I go to the settings menu then back to the main screen there's no recyclerview. Logcat shows the error "W/RecyclerView: No adapter attached; skipping layout"
MainActivity.kt
package com.example.shoppinglist
import android.app.Dialog
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.findNavController
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.floatingactionbutton.FloatingActionButton
class MainActivity : AppCompatActivity(), RVAdapter.ListItemClickInterface {
lateinit var itemsRV: RecyclerView
lateinit var addFAB: FloatingActionButton
lateinit var list: List<ListItems>
lateinit var RVAdapter: RVAdapter
lateinit var viewModel: ViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupActionBarWithNavController(findNavController(R.id.fragment_main))
itemsRV = findViewById(R.id.recyclerView)
addFAB = findViewById(R.id.idFABAdd)
list = ArrayList<ListItems>()
RVAdapter = RVAdapter(list, this)
itemsRV.layoutManager = LinearLayoutManager(this)
itemsRV.adapter = RVAdapter
val repository = Repository(Database(this))
val factory = ViewModelFactory(repository)
viewModel = ViewModelProvider(this, factory).get(ViewModel::class.java)
viewModel.getAllListItems().observe(this, Observer {
RVAdapter.list = it
RVAdapter.notifyDataSetChanged()
})
addFAB.setOnClickListener {
openDialog()
}
}
override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.fragment_main)
return navController.navigateUp() || super.onSupportNavigateUp()
}
fun openDialog() {
val dialog = Dialog(this)
dialog.setContentView(R.layout.add_dialog)
val cancelButton = dialog.findViewById<Button>(R.id.idBtnCancel)
val addButton = dialog.findViewById<Button>(R.id.idBtnAdd)
val itemEdt = dialog.findViewById<EditText>(R.id.idEditItemName)
val itemQuantityEdt = dialog.findViewById<EditText>(R.id.idEditItemQuantity)
cancelButton.setOnClickListener {
dialog.dismiss()
}
addButton.setOnClickListener {
val itemName: String = itemEdt.text.toString()
val itemQuantity: String = itemQuantityEdt.text.toString()
if (itemName.isNotBlank() && itemQuantity.isNotBlank()) {
val items = ListItems(itemName, itemQuantity)
viewModel.insert(items)
Toast.makeText(applicationContext, "Item Added", Toast.LENGTH_SHORT).show()
RVAdapter.notifyDataSetChanged()
dialog.dismiss()
} else {
Toast.makeText(applicationContext, "Enter All Info To Add Item",
Toast.LENGTH_SHORT)
.show()
}
}
dialog.show()
}
override fun onItemClick(listItems: ListItems) {
viewModel.delete(listItems)
RVAdapter.notifyDataSetChanged()
Toast.makeText(applicationContext, "Item Deleted", Toast.LENGTH_SHORT).show()
}
}
HomeFragment.kt
package com.example.shoppinglist
import android.os.Build
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.RequiresApi
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
import kotlinx.android.synthetic.main.fragment_home.*
class HomeFragment : Fragment() {
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)
}
#RequiresApi(Build.VERSION_CODES.N)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
idFABSettings.setOnClickListener {
findNavController().navigate(R.id.action_homeFragment_to_settingsFragment)
}
loadSettings()
}
#RequiresApi(Build.VERSION_CODES.N)
private fun loadSettings(){
val sp = PreferenceManager.getDefaultSharedPreferences(context)
val theme = sp.getBoolean("theme_switch",false)
}
}
SettingsFragment.kt
package com.example.shoppinglist
import android.os.Bundle
import androidx.preference.PreferenceFragmentCompat
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<fragment
android:id="#+id/fragment_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/navigation"
/>
</RelativeLayout>
fragment_home.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="?attr/colorPrimary"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="#layout/list_rv_item"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/idFABAdd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_alignParentBottom="true"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="20dp"
android:backgroundTint="?attr/colorSecondary"
android:contentDescription="Add Item Button"
android:src="#drawable/ic_add"
android:tint="#android:color/white" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/idFABSettings"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="20dp"
android:backgroundTint="?attr/colorSecondary"
android:contentDescription="Add Item Button"
android:src="#drawable/ic_settings"
android:tint="#android:color/white" />
</RelativeLayout>
navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/navigation"
app:startDestination="#id/homeFragment">
<fragment
android:id="#+id/settingsFragment"
android:name="com.example.shoppinglist.SettingsFragment"
android:label="Settings" >
<action
android:id="#+id/action_settingsFragment_to_homeFragment"
app:destination="#id/homeFragment" />
</fragment>
<fragment
android:id="#+id/homeFragment"
android:name="com.example.shoppinglist.HomeFragment"
android:label="Shopping List"
tools:layout="#layout/fragment_home" >
<action
android:id="#+id/action_homeFragment_to_settingsFragment"
app:destination="#id/settingsFragment" />
</fragment>
</navigation>
Not sure if any more info is required. Apologies in advance - I am new to android studio & kotlin.
You are trying to work with views that are specific to HomeFragment's layout through the MainActivity. This will not work correctly. The first time you start the activity, in onCreate you use findViewById to find the view with ID recyclerView, and it successfully finds it because at that time the view is part of the HomeFragment, which is in the layout.
However, when you change fragments, the original fragment view is detached and removed. When you return to the first fragment, Android creates a new instance of your HomeFragment and its layout. The MainActivity is still referencing the original RecyclerView from the first instance of the HomeFragment, so it will update that view that is no longer on screen and it won't touch the new view. You have actually created a memory leak where the MainActivity is preventing that first fragment's view from being destroyed.
Another issue that you haven't discovered yet possibly is that if you rotate the screen while the SettingsFragment is open, you'll crash with a NullPointerException. This is because a screen rotation will cause a new Activity to be created, so onCreate() will be called again, and when it tries to find recyclerView when the HomeFragment is not in the layout, it will fail.
It doesn't make sense to work with a Fragment's specific views from the hosting Activity. The Activity should not have to know any details about what is in a Fragment and what to do with the contents of that fragment. All your code that has to do with the RecyclerView should be in HomeFragment.

Why does Unexpected implicit cast to `EditText`: layout tag was `TextView`

I have a problem, I'm trying to finish the lesson Android fundamentals 02.3: Implicit intents, but there are some errors
the first one is Unexpected implicit cast to EditText: layout tag was TextView
the second one is Consider adding a declaration to your manifest when calling this \ method; see https://g.co/dev/packagevisibility for details
when running the application, it automatically stops immediately, I have tried it on the emulator and on real devices
source codelab : https://developer.android.com/codelabs/android-training-activity-with-implicit-intent?index=..%2F..%2Fandroid-training#0
Code MainActivity.java
package com.example.implicitintents;
import android.content.Intent;
import android.net.Uri;
import android.support.v4.app.ShareCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
public class MainActivity extends AppCompatActivity {
private EditText mWebsiteEditText;
private EditText mLocationEditText;
private EditText mShareTextEditText;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebsiteEditText = findViewById(R.id.website_edittext);
mLocationEditText = findViewById(R.id.location_edittext);
mShareTextEditText = findViewById(R.id.share_edittext);
}
public void openWebsite(View view) {
String url = mWebsiteEditText.getText().toString();
Uri webpage = Uri.parse(url);
Intent intent = new Intent(Intent.ACTION_VIEW, webpage);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
} else {
Log.d("ImplicitIntents", "Can't handle this intent!");
}
}
public void openLocation(View view) {
String loc = mLocationEditText.getText().toString();
Uri addressUri = Uri.parse("geo:0,0?q=" + loc);
Intent intent = new Intent(Intent.ACTION_VIEW, addressUri);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
} else {
Log.d("ImplicitIntents", "Can't handle this intent!");
}
}
public void shareText(View view) {
String txt = mShareTextEditText.getText().toString();
String mimeType = "text/plain";
ShareCompat.IntentBuilder
.from(this)
.setType(mimeType)
.setChooserTitle("Share this text with: ")
.setText(txt)
.startChooser();
}
}
Code Activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context="MainActivity">
<TextView
android:id="#+id/website_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/edittext_uri" />
<Button
android:id="#+id/open_website_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:onClick="openWebsite"
android:text="#string/button_uri" />
<TextView
android:id="#+id/location_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/edittext_loc" />
<Button
android:id="#+id/open_location_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:onClick="openLocation"
android:text="#string/button_loc" />
<TextView
android:id="#+id/share_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/edittext_share" />
<Button
android:id="#+id/share_text_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:onClick="shareText"
android:text="#string/button_share" />
</LinearLayout>
In your xml file you declare three elements as Textview
<TextView
android:id="#+id/website_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/edittext_uri" />
<TextView
android:id="#+id/location_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/edittext_loc" />
<TextView
android:id="#+id/share_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/edittext_share" />
And then in your activity you declare your variables as EditText
private EditText mWebsiteEditText;
private EditText mLocationEditText;
private EditText mShareTextEditText;
And that's why your seeing those errors of casting.
Change in your xml file to EditText if you want to input some data or leave it like TextView if you want display some text but change the type of your variables in the activity. Make sure both are the same type

Handeling multiple User Interfaces with different bottom navigations in Android Studio

I am developing an app where there are different user interfaces depending on what kind of client u are. I wanted to create different Bottom Navigations depending on what type of user is logged in. The if- clause works, so the Log tells me the correct user type but I am getting a fatal exception because it tells me that the id of the second bottom navigation is not existent, but like the first one works. I now it's not the cleanest way to do so but I couldn't find a different way. Here is my code:
This is the main.kt
class MainActivity : AppCompatActivity() {
lateinit var homeFragment: HomeFragment
lateinit var mapsFragment: MapsFragment
lateinit var profil: Profil
lateinit var chat: Chat
lateinit var homeFoto: HomeFoto
val COARSE_LOCATION_RQ = 101
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val mAuth = FirebaseAuth.getInstance()
lateinit var mDatabase : DatabaseReference
val user = mAuth.currentUser
val uid = user!!.uid
var snapshot: DataSnapshot
var anwender = "Suchender"
mDatabase = FirebaseDatabase.getInstance().getReference("User").child(uid)
//Log.e("keyKey",mDatabase.database.getReference("Anwendertyp").child(anwender).toString())
mDatabase!!.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.exists() && snapshot.child("Anwendertyp").value.toString() == "Suchender" ){
Log.e("keyKey",snapshot.child("Anwendertyp").value.toString())
//findNavController(R.id.suchenderNavigation)
var bottomnav = findViewById<BottomNavigationView>(R.id.BottomNavMenu)
bottomnav.setOnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_home -> {
homeFragment = HomeFragment()
supportFragmentManager
.beginTransaction()
.replace(R.id.frameLayout, homeFragment)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.commit()
}
R.id.mapsFragment -> {
mapsFragment = MapsFragment()
supportFragmentManager
.beginTransaction()
.replace(R.id.frameLayout, mapsFragment)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.commit()
}
R.id.navigation_notifications -> {
profil = Profil()
supportFragmentManager
.beginTransaction()
.replace(R.id.frameLayout, profil)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.commit()
}
}
true
}
}
else{
setContentView(R.layout.startseite_fotografvideograf)
Log.e("key",snapshot.child("Anwendertyp").value.toString())
// Navigation.findNavController(HomeFoto().requireActivity(), R.id.navigation_home_fotografvideograf)
//findNavController(R.id.fotografNavigation)
//This is the variable that triggers the fatal exception
var bottomn = findViewById<BottomNavigationView>(R.id.bottomNavFoto)
Log.e("heyo", bottomn.toString())
bottomn.setOnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_home_fotografvideograf -> {
homeFoto = HomeFoto()
supportFragmentManager
.beginTransaction()
.replace(R.id.frameLayout, homeFoto)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.commit()
}
R.id.navigation_notifications -> {
profil = Profil()
supportFragmentManager
.beginTransaction()
.replace(R.id.frameLayout, profil)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.commit()
}
R.id.chatchat -> {
chat = Chat()
supportFragmentManager
.beginTransaction()
.replace(R.id.frameLayout, chat)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.commit()
}
}
true
}
}
}
override fun onCancelled(error: DatabaseError) {
}
})
}
This is the main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#E8E8E8">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottomNavigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_margin="30dp"
android:background="#drawable/buttom_bg"
android:elevation="2dp"
app:itemIconSize="30dp"
app:itemIconTint="#drawable/item_selector"
app:itemRippleColor="#android:color/transparent"
app:labelVisibilityMode="unlabeled"
app:menu="#menu/bottom_nav_menu" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottomNavFoto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_margin="30dp"
android:background="#drawable/buttom_bg"
android:elevation="2dp"
app:itemIconSize="30dp"
app:itemIconTint="#drawable/item_selector"
app:itemRippleColor="#android:color/transparent"
app:labelVisibilityMode="unlabeled"
app:menu="#menu/bottom_nav_fotograf" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
And here is the xml of the second bottom navigation:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/BottomNavFotograf"
>
<item
android:id="#+id/navigation_home_fotografvideograf"
android:icon="#drawable/ic_home_black_24dp"
android:title="#string/title_home" />
<item
android:id="#+id/navigation_notifications"
android:icon="#drawable/ic_baseline_person_24"
android:title="#string/profil" />
<item
android:id="#+id/chatchat"
android:icon="#drawable/ic_chat"
android:title="Chat" />
</menu>
Update
This is the FATAL EXCEPTION I'm getting:
E/AndroidRuntime: FATAL EXCEPTION: main Process: com.example.discoverme, PID: 4430 java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String com.google.android.material.bottomnavigation.BottomNavigationView.toString()' on a null object reference at com.example.discoverme.MainActivity$onCreate$1.onDataChange(MainActivity.kt:114) at com.google.firebase.database.core.ValueEventRegistration.fireEvent(ValueEventRegistration.java:75) at com.google.firebase.database.core.view.DataEvent.fire(DataEvent.java:63) at – Luzie Ewert 19 hours ago Delete
com.google.firebase.database.core.view.EventRaiser$1.run(EventRaiser.java:55) at android.os.Handler.handleCallback(Handler.java:751) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:154)
like, it's so confusing. I know it has to be something with the id's or the fact that there can not be two bottom navigations in one layout but I can't help myself any other way and I can't tell where the first id of the first bottom navigation is coming from, because it is nowhere declared and if I'm using one of the id's I used in the xml's the app crashes and I am getting trhe same fatal (axception that tells me the reference is on a null object.
okay so update: I found the correct id. Apparently there have been two existing main.xml files and I had to delete one, but now I am only getting one of the bottom navigations, no matter what user (wether searching person or photographer) is logged in. The question is still, how can I separate them?
Here is the updated xml:
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#E8E8E8">
<FrameLayout
android:id="#+id/frameLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/BottomNavMenu"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center"
android:layout_margin="30dp"
android:background="#drawable/buttom_bg"
android:elevation="2dp"
app:itemIconSize="30dp"
app:itemIconTint="#drawable/item_selector"
app:itemRippleColor="#android:color/transparent"
app:labelVisibilityMode="unlabeled"
app:menu="#menu/bottom_nav_menu"
tools:ignore="MissingConstraints">
</com.google.android.material.bottomnavigation.BottomNavigationView>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottomNavFoto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_margin="30dp"
android:background="#drawable/buttom_bg"
android:elevation="2dp"
app:itemIconSize="30dp"
app:itemIconTint="#drawable/item_selector"
app:itemRippleColor="#android:color/transparent"
app:labelVisibilityMode="unlabeled"
app:layout_constraintBottom_toBottomOf="#+id/frameLayout"
app:menu="#menu/bottom_nav_fotograf"
tools:ignore="MissingConstraints"
tools:layout_editor_absoluteX="30dp"></com.google.android.material.bottomnavigation.BottomNavigationView>
</androidx.constraintlayout.widget.ConstraintLayout>
Okay, I managed it by outsourcing the bottom navigations in new activities and starting those in the if-clause of the main activity.kt with new layouts.

Resources