In my SettingActivity I'm loading some data to be shown in ListPreference from Room, once the items are selected all works correctly, the value is saved to `SharedPreferences and the summary is shown correctly, but once I return to SettingsActivity the summary value is reset to null.
Here is what is happening:
My code is pretty simple, onViewCreated() I start observing LiveData to be shown in ListPreference and then I set the values for entries and entryValues
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.shops.observe(viewLifecycleOwner) {
setShopsPreferences(it)
}
}
private fun setShopsPreferences(shops: List<Shop>) {
val shopsPreference = preferenceManager.findPreference<ListPreference>("defaultShop")
if (shops.isEmpty()) {
shopsPreference?.isEnabled = false
return
} else {
shopsPreference?.isEnabled = true
}
val entries: ArrayList<String> = ArrayList()
val entryValues: ArrayList<String> = ArrayList()
shops.forEach {
entries.add(it.description)
entryValues.add(it.id)
}
shopsPreference?.entryValues = entryValues.toArray(arrayOfNulls<CharSequence>(entryValues.size))
shopsPreference?.entries = entries.toArray(arrayOfNulls<CharSequence>(entries.size))
}
ViewModel:
#HiltViewModel
class ShopsViewModel #Inject constructor(repository: ShopsRepository) : ViewModel() {
private val _shops = MutableLiveData<List<Shop>>()
val shops: LiveData<List<Shop>> = _shops
init {
repository.getAllShops().observeForever {
_shops.value = it
}
}
}
Repository:
fun getAllShops(): LiveData<List<Shop>> {
return shops.select()
}
DAO:
#Dao
interface ShopsDAO {
#Query("SELECT * FROM shops")
fun select(): LiveData<List<Shop>>
}
You are populating list entries dynamically after the preference hierarchy is already created by inflating the xml. But during that time there was no entry, hence the value was null. The data was then retrieved asynchronously but the change will not be reflected. So you have to set the summary manually.
Another approach I'm not sure about is to call recreate on the activity after populating the data inside the observer listener.
To resolve the issue with dynamic data in a ListPreference I've made some changes to my code, first of call in onCreatePreferences() I've added a preference listener to my ListPreference like this:
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
val shopsPreference = preferenceManager.findPreference<ListPreference>("defaultShop")
shopsPreference?.setOnPreferenceChangeListener { preference, newValue ->
val description = viewModel.shops.value?.find { it.id == newValue }
preference.summary = description?.description
true
}
}
So in that way on each new selection, I'm looking for the matching value for that preference in my ViewModel and getting the description for it which then is set as the summary.
While to set the summary every time the list has been changed or the SettingsActivity is opened I've added the following code to my setShopsPreferences() function:
private fun setShopsPreferences(shops: List<Shop>?) {
if (shops == null) {
return
}
val shopsPreference = preferenceManager.findPreference<ListPreference>("defaultShop")
// The preference is enabled only if there are shows inside the list
shopsPreference?.isEnabled = shops.isNotEmpty()
// Getting the defaultShop preference value, which will be used to find the actual shop description
val defaultShop = sharedPreferences.getString("defaultShop", "0")
defaultShop?.let { shopId ->
// Setting the summary based on defaultShop saved preference
shopsPreference?.summary = shops.find { it.id == shopId }?.description
}
val entries: ArrayList<String> = ArrayList()
val entryValues: ArrayList<String> = ArrayList()
shops.forEach {
entries.add(it.description)
entryValues.add(it.id)
}
shopsPreference?.entryValues =
entryValues.toArray(arrayOfNulls<CharSequence>(entryValues.size))
shopsPreference?.entries = entries.toArray(arrayOfNulls<CharSequence>(entries.size))
}
I have an app that launches the majority of the time, but every 7 or so launches it crashes with the error:
kotlin.UninitializedPropertyAccessException: lateinit property weekdayList has not been initialized
This is a clear error, I'm just not sure how to ensure the variable is initialized early enough in the context of my app.
Things I have tried
I tried moving variables around, making "inner" and "outer"
variables, one within onCreate and an underscore led variable as
the class variable.
Changing the viewmodel so that it will wait until the call to the db has finished (I
couldn't make this work, but mostly because I wasn't sure how to do it).
I think the problem is in the onCreate function, and that the weekday observe isn't setting the value of the variable faster than the task observe (where the weekdayList variable is needed) is called?
Edit 1
I referenced this but I end up with a similar error
java.lang.IndexOutOfBoundsException: Empty list doesn't contain element at index 1.
Edit 2
I understand how lateinit variables and nullables work at this point, I want to try and clarify this a little better.
The variable weekdayList needs to be initialized to the correct list before I hit the observe for the taskList, otherwise the app will crash.
I have tried setting the variable to be nullable, and Iend up with:
skipping parts of the program when it's null (not an option)
crashing with a null pointer exception (if set to non-nullable)
no tasks get assigned to any day, which means no recyclerviews get updated, thus making the app appear to contain no tasks when it does.
weekday buttons that don't work because there is no weekdayList for them to compare against to launch the next activity
My problem doesn't stand in figuring out if it's null or not, it's trying to guarantee that it won't be null.
Sorry for the confusion
Main Activity
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val plannerViewModel: PlannerViewModel by viewModels {
PlannerViewModelFactory((application as PlannerApplication).repository)
}
private var weekdayList: List<Weekday> = listOf()
private var taskList: List<Task> = listOf()
private var taskDayList = mutableListOf<Task>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val clearButtonText = binding.clearCardText
val sundayButtonText = binding.sundayCardText
val mondayButtonText = binding.mondayCardText
val tuesdayButtonText = binding.tuesdayCardText
val wednesdayButtonText = binding.wednesdayCardText
val thursdayButtonText = binding.thursdayCardText
val fridayButtonText = binding.fridayCardText
val saturdayButtonText = binding.saturdayCardText
val sundayRv: RecyclerView = binding.sundayRv
val sundayAdapter = TaskRvAdapter(null)
sundayRv.adapter = sundayAdapter
sundayRv.layoutManager = LinearLayoutManager(this)
val mondayRv: RecyclerView = binding.mondayRv
val mondayAdapter = TaskRvAdapter(null)
mondayRv.adapter = mondayAdapter
mondayRv.layoutManager = LinearLayoutManager(this)
val tuesdayRv: RecyclerView = binding.tuesdayRv
val tuesdayAdapter = TaskRvAdapter(null)
tuesdayRv.adapter = tuesdayAdapter
tuesdayRv.layoutManager = LinearLayoutManager(this)
val wednesdayRv: RecyclerView = binding.wednesdayRv
val wednesdayAdapter = TaskRvAdapter(null)
wednesdayRv.adapter = wednesdayAdapter
wednesdayRv.layoutManager = LinearLayoutManager(this)
val thursdayRv: RecyclerView = binding.thursdayRv
val thursdayAdapter = TaskRvAdapter(null)
thursdayRv.adapter = thursdayAdapter
thursdayRv.layoutManager = LinearLayoutManager(this)
val fridayRv: RecyclerView = binding.fridayRv
val fridayAdapter = TaskRvAdapter(null)
fridayRv.adapter = fridayAdapter
fridayRv.layoutManager = LinearLayoutManager(this)
val saturdayRv: RecyclerView = binding.saturdayRv
val saturdayAdapter = TaskRvAdapter(null)
saturdayRv.adapter = saturdayAdapter
saturdayRv.layoutManager = LinearLayoutManager(this)
// Setting day card names
clearButtonText.text = "Clear"
sundayButtonText.text = "Sun"
mondayButtonText.text = "Mon"
tuesdayButtonText.text = "Tue"
wednesdayButtonText.text = "Wed"
thursdayButtonText.text = "Thu"
fridayButtonText.text = "Fri"
saturdayButtonText.text = "Sat"
sundayButtonText.text = "Sun"
plannerViewModel.allWeekdays.observe(this, {
weekdayList = it
})
plannerViewModel.allTasks.observe(this, { tasks ->
taskList = tasks
taskDayList = mutableListOf()
for (i in 1..7) {
taskDayList = sortTasks(weekdayList[i], taskList)
when (i) {
1 -> {
sundayAdapter.submitList(taskDayList)
toggleVisibility(taskDayList, binding.sundayInner,
binding.sundayCardText, sundayRv, binding.sundayNoTasks)
}
2 -> {
mondayAdapter.submitList(taskDayList)
toggleVisibility(taskDayList, binding.mondayInner,
binding.mondayCardText, mondayRv, binding.mondayNoTasks)
}
3 -> {
tuesdayAdapter.submitList(taskDayList)
toggleVisibility(taskDayList, binding.tuesdayInner,
binding.tuesdayCardText, tuesdayRv, binding.tuesdayNoTasks)
}
4 -> {
wednesdayAdapter.submitList(taskDayList)
toggleVisibility(taskDayList, binding.wednesdayInner,
binding.wednesdayCardText, wednesdayRv, binding.wednesdayNoTasks)
}
5 -> {
thursdayAdapter.submitList(taskDayList)
toggleVisibility(taskDayList, binding.thursdayInner,
binding.thursdayCardText, thursdayRv, binding.thursdayNoTasks)
}
6 -> {
fridayAdapter.submitList(taskDayList)
toggleVisibility(taskDayList, binding.fridayInner,
binding.fridayCardText, fridayRv, binding.fridayNoTasks)
}
7 -> {
saturdayAdapter.submitList(taskDayList)
toggleVisibility(taskDayList, binding.saturdayInner,
binding.saturdayCardText, saturdayRv, binding.saturdayNoTasks)
}
}
}
})
}
private fun toggleVisibility(taskDayList: List<Task>, inner: ConstraintLayout,
cardText: View, rv: RecyclerView, noTask: View) {
if (taskDayList.count() == 0 ) {
val newConstraintSet = ConstraintSet()
newConstraintSet.clone(inner)
newConstraintSet.connect(noTask.id, ConstraintSet.TOP,
cardText.id, ConstraintSet.BOTTOM)
newConstraintSet.applyTo(inner)
newConstraintSet.connect(cardText.id, ConstraintSet.BOTTOM,
noTask.id, ConstraintSet.TOP)
newConstraintSet.applyTo(inner)
rv.visibility = View.GONE
noTask.visibility = View.VISIBLE
Log.i("this", "ran zero")
} else {
val newConstraintSet = ConstraintSet()
newConstraintSet.clone(inner)
newConstraintSet.connect(rv.id, ConstraintSet.TOP,
cardText.id, ConstraintSet.BOTTOM)
newConstraintSet.applyTo(inner)
newConstraintSet.connect(cardText.id, ConstraintSet.BOTTOM,
rv.id, ConstraintSet.TOP)
newConstraintSet.applyTo(inner)
rv.visibility = View.VISIBLE
noTask.visibility = View.GONE
Log.i("this", "ran else")
}
}
private fun sortTasks(day: Weekday, tasks: List<Task>): MutableList<Task> {
val newAdapterList = mutableListOf<Task>()
tasks.forEach {
if (it.weekdayId == day.id) {
newAdapterList.add(it)
}
}
return newAdapterList
}
private fun startWeekdayActivity(day: Weekday) {
val intent = Intent(this, WeekdayActivity::class.java)
intent.putExtra("dayId", day.id)
this.startActivity(intent)
}
private fun clearDb(taskList: List<Task>) {
val alertDialog: AlertDialog = this.let { outerIt ->
val builder = AlertDialog.Builder(outerIt)
builder.apply {
setPositiveButton("Clear",
DialogInterface.OnClickListener { dialog, id ->
if (taskList.count() == 0) {
Toast.makeText(context, "No tasks to clear", Toast.LENGTH_SHORT).show()
} else {
plannerViewModel.deleteAllTasks()
Toast.makeText(context, "Tasks cleared", Toast.LENGTH_SHORT).show()
}
})
setNegativeButton("Cancel",
DialogInterface.OnClickListener { dialog, id ->
// User cancelled the dialog
})
}
.setTitle("Clear tasks?")
.setMessage("Are you sure you want to clear the weeks tasks?")
builder.create()
}
alertDialog.show()
}
private fun checkDay(dayIn: String, weekdayList: List<Weekday>) {
weekdayList.forEach {
if (dayIn == "clear_card" && it.day == "Clear") {
clearDb(taskList)
} else {
val dayInAbr = dayIn.substring(0, 3).toLowerCase(Locale.ROOT)
val dayOutAbr = it.day.substring(0, 3).toLowerCase(Locale.ROOT)
if (dayInAbr == dayOutAbr) {
startWeekdayActivity(it)
}
}
}
}
fun buttonClick(view: View) {
when (view.id) {
R.id.clear_card -> checkDay(view.context.resources.getResourceEntryName(R.id.clear_card).toString(), weekdayList)
R.id.sunday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.sunday_card).toString(), weekdayList)
R.id.monday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.monday_card).toString(), weekdayList)
R.id.tuesday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.tuesday_card).toString(), weekdayList)
R.id.wednesday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.wednesday_card).toString(), weekdayList)
R.id.thursday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.thursday_card).toString(), weekdayList)
R.id.friday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.friday_card).toString(), weekdayList)
R.id.saturday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.saturday_card).toString(), weekdayList)
}
}
}
Viewmodel
class PlannerViewModel(private val repository: DbRepository) : ViewModel() {
val allWeekdays: LiveData<List<Weekday>> = repository.allWeekdays.asLiveData()
val allTasks: LiveData<List<Task>> = repository.allTasks.asLiveData()
fun insertWeekday(weekday: Weekday) = viewModelScope.launch {
repository.insertWeekday(weekday)
}
fun insertTask(task: Task) = viewModelScope.launch {
repository.insertTask(task)
}
fun deleteTask(task: Task) = viewModelScope.launch {
repository.deleteTask(task)
}
fun deleteAllTasks() = viewModelScope.launch {
repository.deleteAllTasks()
}
}
class PlannerViewModelFactory(private val repository: DbRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(PlannerViewModel::class.java)) {
#Suppress("UNCHECKED_CAST")
return PlannerViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
A variable declared as lateinit just means that you are sure that when the object is dereferenced it will not be null. In your case you are calling a method from weekdayList object before it is assigned a value. It is important to understand the concept clearly and why your code works.
Happy Coding!
You can use the "isInitialized" method, for checking "lateinit" variable is initialized or not.
Please refer the below article for the this-
https://blog.mindorks.com/how-to-check-if-a-lateinit-variable-has-been-initialized
lateinit is a way for you to have a var without an initial value when you declare it. It's a nice way to avoid taking something that will never be null, and making it nullable (and having to null check it forever) just so you can temporarily set it to null as a placeholder that nothing will ever see.
What you're doing is promising the compiler "ok, I'm not going to provide a value when the class is constructed, but I promise I'll set it to something before anything tries to read it". You're telling the compiler to trust you, that you know how your code works, and you can guarantee it'll all be ok.
Your problem is that it seems you can't guarantee that things won't try to read that property before you write to it. Your state can either be "has a value", or "doesn't have a value", and the rest of your code could encounter either state.
The "no value" state is basically null, so you should probably make the variable nullable instead, and initialise it as null. Kotlin has all that nice null-safety stuff to help your code handle it, until you do get a value. lateinit seems like the wrong tool for the job, even if you check ::isInitialized it's just making your life a lot harder when the null-checking stuff is right there!
Use lazy properties , refer to this doc for more informations:
assuming weekDayList is the property you want to successfully initialize ->
private var weekDayList: List<WeekDay> by lazy {
//return your first value
listOf<WeekDay>()
}
Here is a useful link about LifeCycleAware Lazy properties: blog Although, it is not required.
A solution with help from cactustictacs in the comments.
I moved a lot of the list dependency to a new function called setAdapterList. this allows both observes to run the function, and only the one with both lists initialized will run the code contained. I kept the variables lateinit and it seems to be working so far!
The Main Change in Main Activity
...
private fun setAdapterLists(adapterList: List<TaskRvAdapter>, rvList: List<RecyclerView>) {
if (this::weekdayList.isInitialized && this::taskList.isInitialized) {
adapterList.forEach {
taskDayList = mutableListOf()
val i = adapterList.indexOf(it)
taskDayList = sortTasks(weekdayList[i + 1], taskList)
Log.i("rvli", rvList[i].toString())
when (i) {
0 -> {
adapterList[i].submitList(taskDayList)
toggleVisibility(taskDayList, binding.sundayInner,
binding.sundayCardText, rvList[i], binding.sundayNoTasks)
}
1 -> {
adapterList[i].submitList(taskDayList)
toggleVisibility(taskDayList, binding.mondayInner,
binding.mondayCardText, rvList[i], binding.mondayNoTasks)
}
2 -> {
adapterList[i].submitList(taskDayList)
toggleVisibility(taskDayList, binding.tuesdayInner,
binding.tuesdayCardText, rvList[i], binding.tuesdayNoTasks)
}
3 -> {
adapterList[i].submitList(taskDayList)
toggleVisibility(taskDayList, binding.wednesdayInner,
binding.wednesdayCardText, rvList[i], binding.wednesdayNoTasks)
}
4 -> {
adapterList[i].submitList(taskDayList)
toggleVisibility(taskDayList, binding.thursdayInner,
binding.thursdayCardText, rvList[i], binding.thursdayNoTasks)
}
5 -> {
adapterList[i].submitList(taskDayList)
toggleVisibility(taskDayList, binding.fridayInner,
binding.fridayCardText, rvList[i], binding.fridayNoTasks)
}
6 -> {
adapterList[i].submitList(taskDayList)
toggleVisibility(taskDayList, binding.saturdayInner,
binding.saturdayCardText, rvList[i], binding.saturdayNoTasks)
}
}
}
}
}
...
I'm trying to make random password generator based on user input and everything is fine until i use .toCharArray().shuffle() function, but without shuffling it's too predictable beacuse it puts letters in pre-determined positions. Is there any way this code would work? Any workaround? I already tried stringbuilder but it bypasses user input so I don't know what to do now.
val chars= "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~##$%^&*()!"
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
if (fromUser)
{
when(seekBar)
{
sbNumberOfLetters ->
{
tvLetterCount.text = progress.toString()
smallLetters = progress
}
sbNumberOfCapitalLetters ->
{
tvCapitalsCount.text = progress.toString()
capitalLetterNumber = progress
}
sbNumberOfNumerals ->
{
tvNumeralsCount.text = progress.toString()
numeralsNumber = progress
}
sbNumberOfSpecialChars ->
{
tvSpecialCharsCount.text = progress.toString()
specialCharNumber = progress
}
}
}
}
private fun generatePassword() {
for (y in 1..numeralsNumber)
{
var randomLetter = Random.nextInt(0, 9)
listOfLetters.add(chars[randomLetter].toString())
}
for (w in 1..smallLetters)
{
var randomLetter = Random.nextInt(10, 36)
listOfLetters.add(chars[randomLetter].toString())
}
for (x in 1..capitalLetterNumber)
{
var randomLetter = Random.nextInt(36, 62)
listOfLetters.add(chars[randomLetter].toString())
}
for (z in 1..specialCharNumber)
{
var randomLetter = Random.nextInt(63, 73)
listOfLetters.add(chars[randomLetter].toString())
}
password = (listOfLetters.joinToString(separator = "",)).toCharArray().shuffle().toString()
tvGeneratedPassword.text = password
listOfLetters.clear()
}
shuffle returns Unit, so calling toString() on Unit will return kotlin.Unit which is defined in Unit. Try this:
listOfLetters.shuffle()
val password = listOfLetters.joinToString(separator = "")
tvGeneratedPassword.text = password
Might as well use the tools that Kotlin provides to write expressive and maintainable code ( are the indexes for Random.nextInt in the code you provided correct? ) . Here is one alternative (here lists and not arrays are used and shuffled() returns a new list ) :
abstract sealed class RandomChars {
open val chars: CharArray = charArrayOf()
fun get(count: Int) = (1..count).map { chars[Random.nextInt(0, chars.size)] }
}
object RandomDigits : RandomChars() {
override val chars = "0123456789".toCharArray()
}
object RandomLowerCase : RandomChars() {
override val chars = "abcdefghijklmnopqrstuvwxyz".toCharArray()
}
object RandomUpperCase : RandomChars() {
override val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray()
}
object RandomSpecial : RandomChars() {
override val chars = "~##$%^&*()!".toCharArray()
}
fun main() {
val password =
(RandomDigits.get(1) + RandomLowerCase.get(2) + RandomUpperCase.get(2) + RandomSpecial.get(1))
.shuffled()
.joinToString(separator = "")
println(password) // e.g. %Oa7Mt
}
Here is another, more functional approach:
val charGenerator =
{ alphabet: CharArray -> { count: Int -> (1..count).map { alphabet[Random.nextInt(0, alphabet.size)] } } }
val randomDigits = charGenerator("0123456789".toCharArray())
val randomLowecase = charGenerator("abcdefghijklmnopqrstuvwxyz".toCharArray())
val randomUppercase = charGenerator("ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray())
val randomSpecial = charGenerator("~##$%^&*()!".toCharArray())
fun main() {
val password =
(randomDigits(1) + randomLowecase(2) + randomUppercase(2) + randomSpecial(1))
.shuffled()
.joinToString(separator = "")
println(password) // e.g. %Oa7Mt
}
I don't know why using this Fragment can make the problem , it seems that isIconSpaceReserved = false is useful for not preferenceCagory
class TFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
val context = preferenceManager.context
val screen = preferenceManager.createPreferenceScreen(context)
val category1 = PreferenceCategory(context)
category1.title = "button"
category1.summary = "what is the problem"
category1.key = "bbb"
val swithButton = SwitchPreferenceCompat(context)
swithButton.title = "button"
swithButton.summary = "summary"
swithButton.key = "sss"
swithButton.isIconSpaceReserved = false
val swithButton2 = EditTextPreference(context)
swithButton2.title = "edit"
val swithButton3 = SeekBarPreference(context)
swithButton3.title = "seekbar"
swithButton3.value = 1
screen.addPreference(category1)
category1.addPreference(swithButton)
val category2 = PreferenceCategory(context)
category2.title = "category2"
screen.addPreference(category2)
category2.addPreference(swithButton2)
category2.addPreference(swithButton3)
preferenceScreen = screen
}
style is normal, I try so many ways but it's useless.
now, I tried the 'androidx.preference:preference:1.1.0-alpha01' , so lucky, It fixed the bug that the isIconSpaceReserved was not supported for PreferenceCategory.
override fun setPreferenceScreen(preferenceScreen: PreferenceScreen?) {
super.setPreferenceScreen(preferenceScreen)
if (preferenceScreen != null) {
val count = preferenceScreen.preferenceCount
for (i in 0 until count) {
if (preferenceScreen.getPreference(i) is PreferenceCategory) {
val category = preferenceScreen.getPreference(i) as PreferenceCategory
val childCount = category.preferenceCount
for (j in 0 until childCount) {
category.getPreference(j).isIconSpaceReserved = false
}
}
preferenceScreen.getPreference(i).isIconSpaceReserved = false
}
}
}
PROBLEM - After a note is deleted from second activity, on returning back to first activity(this activity displays notes), changed made to note i.e deleted or edited does not shows change UNLESS the app is restarted and onCreate() method is recalled. If I change my device orientation, then the data gets updated.
How my code works - Basically, my app consists of two activities. First(Main) Activity is where recyclerview resides, this activity handles display of notes by fetching data from SQLite database and displays in form of cardViews. Those cardViews are click able, each cardView when clicked takes to Second(Reference) activity and a corresponding data is loaded into that activity. Now a user has a choice to either make changes to current note or to delete it. If a user clicks on delete button, data of the corresponding note is deleted from SQLite database. On deletion, app automatically goes back to Main activity. HOWEVER, the deleted note does not appears to be deleted in the main activity not until the app is restarted and onCreate method is called.
I have gone through multiple almost similar questions on the site but they do not appear to fit my needs. I am a beginner in Android development so if you could please explain it a little would greatly help me. Thank you.
MAIN ACTIVITY
class MainActivity : AppCompatActivity() {
//START OF EX-INITIALIZATIONS
var dbHandler: PediaDatabase? = null
var adapter: PediaAdapter? = null
var layoutManager: RecyclerView.LayoutManager? = null
var list: ArrayList<UserNotes>? = ArrayList()
var listItems: ArrayList<UserNotes>? = ArrayList()
val PREFS_NAME: String = "MYPREFS"
var myPrefs: SharedPreferences? = null
var first_run: Boolean = true
val REQUEST_CODE: Int = 1
var deletedNoteID: Int = 0
var deletedNoteAdapterPos: Int = 0
//END OF EX-INITIALIZATIONS
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
showOneTimeMessage()
invalidateOptionsMenu()
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
window.navigationBarColor = Color.BLACK
}
//START OF IN-INITIALIZATIONS
dbHandler = PediaDatabase(this)
list = ArrayList<UserNotes>()
listItems = ArrayList()
adapter = PediaAdapter(this, listItems!!)
layoutManager = LinearLayoutManager(this)
recyclerViewID.adapter = adapter
recyclerViewID.layoutManager = layoutManager
//END OF IN-INITIALIZATIONS
//DATA POPULATION STARTS HERE
list = dbHandler!!.readAllNotes()
for(reader in list!!.iterator())
{
var note = UserNotes()
note.noteTitle = reader.noteTitle
note.noteText = reader.noteText
note.noteID = reader.noteID
note.noteDate = reader.noteDate
listItems!!.add(note)
}
adapter!!.notifyDataSetChanged()
//DATA POPULATION ENDS HERE
if(dbHandler!!.totalNotes() == 0) {
recyclerViewID.visibility = View.GONE
}
else{
recyclerViewID.visibility = View.VISIBLE
showWhenEmptyID.visibility = View.GONE
}
}//end onCreate
override fun onRestart() {
super.onRestart()
overridePendingTransition(R.anim.slide_out, R.anim.slide_in)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.top_menu, menu)
val item = menu!!.findItem(R.id.delete_note_menu)
item.setVisible(false)
return true
//return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
if(item!!.itemId == R.id.add_note_menu){
var isNewNote = Intent(this, ReferenceActivity::class.java)
isNewNote.putExtra("isNewNote", true)
startActivityForResult(isNewNote, REQUEST_CODE)
}
if(item!!.itemId == R.id.delete_note_menu)
{
Toast.makeText(this,"DELETED", Toast.LENGTH_SHORT).show()
}
return super.onOptionsItemSelected(item)
}
private fun showOneTimeMessage()
{
var data: SharedPreferences = getSharedPreferences(PREFS_NAME, 0)
if(data.contains("isShown"))
{
first_run = data.getBoolean("isShown", true)
}
Log.d("FIRST_RUN", first_run.toString())
if(first_run) {
val oneTimeMsg = SweetAlertDialog(this)
oneTimeMsg.setTitleText("Hey there!")
oneTimeMsg.setContentText("Thank you for downloading! Please don`t forget to rate our app :)").show()
oneTimeMsg.setConfirmClickListener(object : SweetAlertDialog.OnSweetClickListener {
override fun onClick(sweetAlertDialog: SweetAlertDialog?) {
oneTimeMsg.dismissWithAnimation()
}
}).show()
myPrefs = getSharedPreferences(PREFS_NAME, 0)
var editor: SharedPreferences.Editor = (myPrefs as SharedPreferences).edit()
editor.putBoolean("isShown", false)
editor.commit()
}
}
REFERENCE ACTVITY
class ReferenceActivity : AppCompatActivity() {
var dbHandler: PediaDatabase? = null
var note = UserNotes()
var existingNote = UserNotes()
var noteExisted: Boolean = false
var cardID: Int = 0
var cardAdapterPos: Int? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_reference)
getSupportActionBar()!!.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
getSupportActionBar()!!.setCustomView(R.layout.custom_toolbar);
val dateTxtView = findViewById<View>(resources.getIdentifier("action_bar_title", "id", packageName)) as TextView
overridePendingTransition(R.anim.slide_in, R.anim.slide_out);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
window.navigationBarColor = Color.RED
dbHandler = PediaDatabase(this)
var data = intent
var isNewNote = intent
if(isNewNote != null)
if(isNewNote.extras.getBoolean("isNewNote") != true)
{
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN)
if(data != null)
{
noteExisted = true
this.cardAdapterPos = data.extras.getInt("cardPosition")
cardID = data.extras.getInt("cardID")
existingNote = dbHandler!!.readNote(cardID)
refTitleID.setText(existingNote.noteTitle, TextView.BufferType.EDITABLE)
refTextID.setText(existingNote.noteText, TextView.BufferType.EDITABLE)
dateTxtView.text = existingNote.noteDate.toString()
}
}else{
dateTxtView.text = "New note"
}
}//end onCreate()
override fun onStop() {
super.onStop()
var title: String = refTitleID.text.toString().trim()
var text: String = refTextID.text.toString().trim()
if(existingNote.noteText == text && existingNote.noteTitle == title)
finish()
if(noteExisted)
{
if(TextUtils.isEmpty(title))
title = "No title"
existingNote.noteTitle = title
existingNote.noteText = text
//existingNote.noteDate =
dbHandler!!.updateNote(existingNote)
var dataToMain = this.intent
dataToMain.putExtra("cardID", cardID)
dataToMain.putExtra("cardAdapterPos", cardAdapterPos)
setResult(Activity.RESULT_OK, dataToMain)
finish()
}
else
{
if(TextUtils.isEmpty(title) && TextUtils.isEmpty(text))
{
finish()
}
else
{
if(TextUtils.isEmpty(title))
title = "No title"
note.noteTitle = title
note.noteText = text
dbHandler!!.createNote(note)
finish()
}
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.top_menu, menu)
val addItem: MenuItem = menu!!.findItem(R.id.add_note_menu)
val delItem:MenuItem = menu.findItem(R.id.delete_note_menu)
addItem.setVisible(false)
delItem.setVisible(false)
if(noteExisted)
delItem.setVisible(true)
return true
//return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
if(item!!.itemId == R.id.delete_note_menu)
{
val dialogMsg = SweetAlertDialog(this, SweetAlertDialog.WARNING_TYPE)
dialogMsg.setTitleText("Are you sure?")
dialogMsg.setContentText("You won`t be able to recover this note!")
dialogMsg.setConfirmText("Yes,delete it!")
dialogMsg.setConfirmClickListener(object: SweetAlertDialog.OnSweetClickListener {
override fun onClick(sweetAlertDialog: SweetAlertDialog?) {
dialogMsg.dismissWithAnimation()
dbHandler!!.deleteNote(cardID)
var successMsg = SweetAlertDialog(sweetAlertDialog!!.context, SweetAlertDialog.SUCCESS_TYPE)
successMsg.setTitleText("Note deleted!")
successMsg.setContentText("So long,note").show()
successMsg.setCancelable(false)
//TODO Disable 'OK' button on successMsg dialogbox
Handler().postDelayed({
successMsg.dismissWithAnimation()
finish()
}, 1200)
}
}).show()
}
return super.onOptionsItemSelected(item)
}
}
you need to update your list items inside your adapter;
not sure how it works on kotlin, but I use something like this:
after a note update call adapter.updateItens(itens);
MyAdapter extendes RecyclerView.Adapter<MyViewHolder>
private List<MyItem> elements;
MyAdapter(){
this.elements = new ArrayList<>();
}
void updateElements(List<MyItem> itens){
Collections.sort(itens, new SortByName());
this.elements.clear();
this.elements.addAll(itens);
notifyDataSetChanged();
}
you can do even better if instead of notifyDataSetChanged(), you implement a DiffUtil;
After making changes to the data in the database, the RecyclerViewAdapter needs to be given a new list of data. This should be done in onRestart(), so that once you navigate back to MainActivity from SecondActivity, the RecyclerView is populated with the updated data. Try copying the code that populates the RecyclerView and put it into onRestart(). The reason why it was only updating when onCreate() was called is because that's the only place where you do anything to the view.