function call in onActivityCreated() triggers Observer every time the app restarts - kotlin

in my fragment I am observing a live data in a function and in that observer, some sharedPreferences are changed. The function is then called inside onActivityCreated() .The problem is whenever I restart my app the onActivityCreated() gets called which in turn calls that function which in turn observes the live data and thus changes the value of sharedPreference which I don't want.
code to my fragment is attached.
package com.example.expensemanager.ui
import android.app.AlertDialog
import android.content.Context
import android.content.SharedPreferences
import android.graphics.Color
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.expensemanager.R
import com.github.mikephil.charting.data.PieData
import com.github.mikephil.charting.data.PieDataSet
import com.github.mikephil.charting.data.PieEntry
import kotlinx.android.synthetic.main.fragment_transaction_list.*
import kotlinx.android.synthetic.main.set_balance_details.view.*
import org.eazegraph.lib.models.PieModel
class TransactionListFragment : Fragment() {
//declaring the view model
private lateinit var viewModel: TransactionListViewModel
var cashAmount:Float = 0F
var bankAmount:Float = 0F
override fun onCreate(savedInstanceState: Bundle?){
super.onCreate(savedInstanceState)
//setHasOptionsMenu(true)
//(activity as AppCompatActivity?)!!.setSupportActionBar(addAppBar)
viewModel = ViewModelProvider(this)
.get(TransactionListViewModel::class.java)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_transaction_list, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
//recycler view for showing all the transactions
with(transaction_list){
layoutManager = LinearLayoutManager(activity)
adapter = TransactionAdapter {
findNavController().navigate(
TransactionListFragmentDirections.actionTransactionListFragmentToTransactionDetailFragment(
it
)
)
}
}
//code for the floating action button in the main screen
add_transaction.setOnClickListener{
findNavController().navigate(
//here id is passed 0 because the transaction is being added the first time
TransactionListFragmentDirections.actionTransactionListFragmentToTransactionDetailFragment(
0
)
)
}
addAppBar.setOnMenuItemClickListener { menuItem ->
when(menuItem.itemId){
R.id.calendar_button -> {
findNavController().navigate(TransactionListFragmentDirections.actionTransactionListFragmentToCalanderViewFragment())
true
}
R.id.monthly_cards -> {
findNavController().navigate(TransactionListFragmentDirections.actionTransactionListFragmentToMonthlyCardsFragment())
true
}
else -> false
}
}
see_all_transactions.setOnClickListener {
findNavController().navigate(TransactionListFragmentDirections.actionTransactionListFragmentToAllTransactionsFragment())
}
//submitting the new list of upcoming Transactions after getting it from the db
viewModel.upcomingTransactions.observe(viewLifecycleOwner, Observer {
(transaction_list.adapter as TransactionAdapter).submitList(it)
})
val sharedPreferences: SharedPreferences = this.requireActivity().getSharedPreferences("OnboardingDetails", Context.MODE_PRIVATE)
val monthlyBudget = sharedPreferences.getFloat("monthlyBudget",0F)
var totalBalance = monthlyBudget*12
net_balance.text = totalBalance.toString()
Log.d("netbalance",totalBalance.toString())
//the net balance (yearly) is calculated wrt the transactions already done
viewModel.sumOfTransactions.observe(viewLifecycleOwner, Observer {
if (it != null) {
totalBalance += it
net_balance.text = totalBalance.toString()
}
})
val budgetPreferences: SharedPreferences =
this.requireActivity().getSharedPreferences("Balance_details", Context.MODE_PRIVATE)
val editor: SharedPreferences.Editor = budgetPreferences.edit()
//setting pie chart initially to 0
setPieChart(budgetPreferences,editor)
//observing the cash details and the bank details to update the text view and the pie chart
observeBalance(budgetPreferences,editor)
//GraphCardView code
//button for setting the balance details
set_balance_details.setOnClickListener {
setBalanceDetails(budgetPreferences,editor)
}
}
//dialog box for setting the balance details
private fun setBalanceDetails(budgetPreferences: SharedPreferences,editor: SharedPreferences.Editor) {
val dialog = LayoutInflater.from(requireContext()).inflate(
R.layout.set_balance_details,
null
)
//AlertDialogBuilder
val mBuilder = AlertDialog.Builder(requireContext())
.setView(dialog)
//show dialog
val mAlertDialog = mBuilder.show()
dialog.save_details.setOnClickListener {
cashAmount = dialog.cash_amount.editText?.text.toString().toFloat()
bankAmount = dialog.bank_amount.editText?.text.toString().toFloat()
//saving the cashAmount and bankAmount to shared preferences for future use
editor.putFloat("cashAmount", cashAmount).apply()
editor.putFloat("bankAmount", bankAmount).apply()
//setting the pie chart with new values
setPieChart(budgetPreferences, editor)
mAlertDialog.dismiss()
}
dialog.cancel_details.setOnClickListener { mAlertDialog.dismiss() }
mAlertDialog.show()
}
private fun observeBalance(budgetPreferences: SharedPreferences,editor: SharedPreferences.Editor) {
//getting the cashAmount and bankAmount and updating the views with live data
var cashAmount = budgetPreferences.getFloat("cashAmount", 0F)
var bankAmount = budgetPreferences.getFloat("bankAmount", 0F)
viewModel.cashAmount.observe(viewLifecycleOwner, Observer {
if (it != null) {
cashAmount += it
cash.text = "CASH : ${cashAmount}"
Log.d("observeCash",cashAmount.toString())
editor.putFloat("cashAmount",cashAmount).apply()//find solution to this
setPieChart(budgetPreferences,editor)
}
})
viewModel.bankAmount.observe(viewLifecycleOwner, Observer {
if (it != null) {
bankAmount+=it
bank.text = "BANK : ${bankAmount}"
Log.d("observeBank",bankAmount.toString())
editor.putFloat("cashAmount",cashAmount).apply()
setPieChart(budgetPreferences,editor)
}
})
setPieChart(budgetPreferences,editor)
}
//https://www.geeksforgeeks.org/how-to-add-a-pie-chart-into-an-android-application/ use this for reference
private fun setPieChart(budgetPreferences: SharedPreferences,editor: SharedPreferences.Editor) {
val cashAmount = budgetPreferences.getFloat("cashAmount", 0f)
val bankAmount = budgetPreferences.getFloat("bankAmount", 0f)
Log.d("pieCank",cashAmount.toString())
Log.d("pieBank",bankAmount.toString())
cash.text = "CASH : ${cashAmount}"
bank.text = "BANK : ${bankAmount}"
val pieEntries = arrayListOf<PieEntry>()
pieEntries.add(PieEntry(cashAmount))
pieEntries.add(PieEntry(bankAmount))
pieChart.animateXY(1000, 1000)
// setup PieChart Entries Colors
val pieDataSet = PieDataSet(pieEntries, "This is Pie Chart Label")
pieDataSet.setColors(
ContextCompat.getColor(requireActivity(), R.color.blue1),
ContextCompat.getColor(requireActivity(), R.color.blue2)
)
val pieData = PieData(pieDataSet)
// setip text in pieChart centre
//piechart.setHoleColor(R.color.teal_700)
pieChart.setHoleColor(getColorWithAlpha(Color.BLACK, 0.0f))
// hide the piechart entries tags
pieChart.legend.isEnabled = false
// now hide the description of piechart
pieChart.description.isEnabled = false
pieChart.description.text = "Expanses"
pieChart.holeRadius = 40f
// this enabled the values on each pieEntry
pieData.setDrawValues(true)
pieChart.data = pieData
}
fun getColorWithAlpha(color: Int, ratio: Float): Int {
var newColor = 0
val alpha = Math.round(Color.alpha(color) * ratio)
val r = Color.red(color)
val g = Color.green(color)
val b = Color.blue(color)
newColor = Color.argb(alpha, r, g, b)
return newColor
}
}
As seen when app restarts , viewModel.cashAmount gets triggered giving undesired outputs.
What can i do to avoid this .

Activities can get recreated a lot, like when you rotate the screen, or if it's in the background and the system destroys it to free up some memory. Right now, every time that happens your code doesn't know whether it's getting the current value, or a brand new one, but one of those should perform a calculation, and the other should just update the display.
The problem is your calculation logic is tied in with the UI state - it's being told what to display, and also deciding if that counts as a new user action or not. And it has no way of knowing that. Your logic needs to go something like
things observe LiveData values -> update to display new values when they come in
user clicks a button -> do calculation with the value they've entered
calculation result returns -> LiveData gets updated with new value
LiveData value changes -> things update to show the new value
that way a calculation happens specifically in response to a user action, like through a button click. LiveData observers only reflect the current state, so it doesn't matter if they see the same value lots of times, they're just redrawing a pie chart or whatever.
You can use a LiveData to watch for a stream of values, but the thing about UI components is that sometimes they're there to see them, and sometimes they're not. And LiveData is specifically made to push updates to active observers, but not inactive ones - and always provide the most recent value to a new observer, or one that becomes active.
So in that case it works more like "here's the current situation", and that fits better with displaying things, where it doesn't matter if you repeat yourself. That's why you can't do this kind of "handle everything exactly one time" thing in your UI - unless you're literally responding to a UI event like a button click

Related

How to share different layouts depending on the current page in kotlin jetpack compose?

I am relatively new to kotlin with jetpack compose and I am having a hard time figuring out how to share the same layout of a page among different pages. The thing is that the layout is conditional to whether the user has logged in or not (among other things).
The basic implementation I have looks something like the following code.
Now, when I click in a LoginButton in the LoginPage, it changes a isLoggedIn variable in the ViewModels, which in turns triggers recomposition and the proper layout is displayed is wrapping the actual page contents. Another example is if the "create account" button is clicked, then the SignUpLayout is wrapping the page contents.
Now, another way I can think (and like better than triggering the recomposition in all the application), is for the LoginButton to call the navigate method in the NavHostController. However I am unsure how to implement the "wrapper layouts" for this other way of doing things.
So my questions are:
Is the current way of navigating around layouts fine? Even though it triggers recomposition from the top most composable (aka ClientApp)?
If I wanted for the LoginButton to make navigation by calling the NavHostController.navigate method instead of changing a variable that triggers recomposition, how will the different layouts be handled?
Is there anything else I am missing? Maybe there is another way of accomplishing this that I am not aware of and I am just over engineering things?
enum class Page(
#StringRes val title: Int,
val layout: String,
val route: String
) {
Profile(
title = R.string.profile_page,
layout = "main",
route = "/profile"),
Home(
title = R.string.home_page,
layout = "main",
route = "/home"),
Login(
title = R.string.login_page,
layout = "none",
route = "/login"),
SignUpUserDetails(
title = R.string.user_details_page,
layout = "sign-up",
route = "/sign-up/user-details"),
SignUpAccountDetails(
title = R.string.account_details_page,
layout = "sign-up",
route = "/sign-up/account-details"),
}
#Composable
fun PageNavHost(
navHostController: NavHostController,
modifier: Modifier = Modifier
) {
NavHost(
navController = navHostController,
startDestination = Page.Home.route,
modifier = modifier
) {
composable(route = Page.Profile.route) {
ProfilePage()
}
composable(route = Page.Home.route) {
HomePage()
}
composable(route = Page.Login.route) {
val viewModel = hiltViewModel<LoginViewModel>()
LoginPage(viewModel, navHostController)
}
composable(route = Page.SignUpUser.route) {
SignUpUserPage()
}
}
}
#Composable
fun ClientApp() {
val navHostController: NavHostController = rememberNavController()
val mainViewModel = hiltViewModel<MainViewModel>()
val signUpViewModel = hiltViewModel<SignUpViewModel>()
val loginViewModel = hiltViewModel<LoginViewModel>()
val isLoggedIn by mainViewModel.sessionHolder.isLoggedIn.collectAsState()
if(!isLoggedIn) {
LoginPage(loginViewModel, navHostController)
} else {
val currentPage = navHostController.currentPage()
when (currentPage?.layout) {
"main" -> MainLayout(mainViewModel, navHostController) { content() }
"sign-up" -> SignUpLayout(navHostController, signUpViewModel) { content() }
else -> content()
}
}
}

How to remove item from mutableList in kotlin

I am scanning a list and adding an unique item in mutableList. Scanning a item through ScanCallback but below example is using for Kotlin Flow for better understanding and make a simple use case. I am giving an example of emiting different types of item.
Basically I want to remove items from the specific condtions :-
when flow data is finished to emit new values.
when emiting an item, if we no longer receive an item within 30 sec then we remove the item from the list.
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.runBlocking
class ItemList {
val scanResultList = mutableListOf<ScanResults>()
fun doSomething(): Flow<ScanResults> = flow {
(0..20).forEach {
delay(200L)
when (it) {
in 10..12 -> {
emit(ScanResults(Device("item is Adding in range of 10 -- 20")))
}
in 15..18 -> {
emit(ScanResults(Device("item is Adding in range of 15 -- 18")))
}
else -> {
emit(ScanResults(Device("item is Adding without range")))
}
}
}
}
fun main() = runBlocking {
doSomething().collectLatest { value ->
handleScanResult(value)
}
}
private fun handleScanResult(result: ScanResults) {
if (!scanResultList.contains(result)) {
result.device?.name?.let {
if (hasNoDuplicateScanResult(scanResultList, result)) {
scanResultList.add(result)
println("Item added")
}
}
}
}
private fun hasNoDuplicateScanResult(value: List<ScanResults>, result: ScanResults): Boolean {
return value.count { it.device == result.device } < 1
}
data class ScanResults(val device: Device? = null)
data class Device(val name: String? = null)
}
I am not adding Set because in SnapshotStateList is not available in jetpack compose.
I'll try to reword the problem in simple terms. I'll say the input is a Flow of some imaginary data class DeviceInfo so it's easier to describe.
Problem:
There is a source flow of DeviceInfos. We want our output to be a Flow of Set<DeviceInfo>, where the Set is all DeviceInfo's that have been emitted from the source in the past 30 seconds.
(If you want, you can convert this output Flow into State, or collect it and update a mutablestateListOf with it, etc.)
Here is a strategy I thought of. Disclaimer: I haven't tested it.
Tag each incoming DeviceInfo with a unique ID (could be based on system time or a UUID). Add each DeviceInfo to a Map with its latest ID. Launch a child coroutine that delays 30 seconds and then removes the item from the map if the ID matches. If newer values have arrived, then the ID won't match so obsolete child coroutines will expire silently.
val sourceFlow: Flow<DeviceInfo> = TODO()
val outputFlow: Flow<Set<DeviceInfo>> = flow {
coroutineScope {
val tagsByDeviceInfo = mutableMapOf<DeviceInfo, Long>()
suspend fun emitLatest() = emit(tagsByDeviceInfo.keys.toSet())
sourceFlow.collect { deviceInfo ->
val id = System.currentTimeMillis()
if (tagsByDeviceInfo.put(deviceInfo, id) == null) {
emitLatest() // emit if the key was new to the map
}
launch {
delay(30.seconds)
if (tagsByDeviceInfo[deviceInfo] == id) {
tagsByDeviceInfo.remove(deviceInfo)
emitLatest()
}
}
}
}
}

Can you change the color of a textview in a recyclerview adapter after a certain condition is met in Main Activity?

I have a basic function that displays the elapsed time every time the button is pressed. I cannot get the logic in MainActivity to transfer to the recyclerview adapter. I simply want the text output color to change to red after the time passes 5 seconds. I have tried to research how to do this for the past week and I cannot find the exact answer. I'm hoping someone can help.
I have tried it with and without the boolean in the data class. I wasn't sure if that was required.
Here is my code:
Main Activity:`
class MainActivity : AppCompatActivity() {
var startTime = SystemClock.elapsedRealtime()
var displaySeconds = 0
private lateinit var binding: ActivityMainBinding
private val secondsList = generateSecondsList()
private val secondsAdapter = Adapter(secondsList)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
recyclerView.adapter = secondsAdapter
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.setHasFixedSize(false)
binding.button.setOnClickListener {
getDuration()
addSecondsToRecyclerView()
}
}
fun getDuration(): Int {
val endTime = SystemClock.elapsedRealtime()
val elapsedMilliSeconds: Long = endTime - startTime
val elapsedSeconds = elapsedMilliSeconds / 1000.0
displaySeconds = elapsedSeconds.toInt()
return displaySeconds
}
private fun generateSecondsList(): ArrayList<Seconds> {
return ArrayList()
}
fun addSecondsToRecyclerView() {
val addSeconds =
Seconds(getDuration(), true)
secondsList.add(addSeconds)
secondsAdapter.notifyItemInserted(secondsList.size - 1)
}
}
Adapter:
var adapterSeconds = MainActivity().getDuration()
class Adapter(
private val rvDisplay: MutableList<Seconds>
) : RecyclerView.Adapter<Adapter.AdapterViewHolder>() {
class AdapterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val textView1: TextView = itemView.tv_seconds
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AdapterViewHolder {
val myItemView = LayoutInflater.from(parent.context).inflate(
R.layout.rv_item,
parent, false
)
return AdapterViewHolder(myItemView)
}
override fun onBindViewHolder(holder: Adapter.AdapterViewHolder, position: Int) {
val currentDisplay = rvDisplay[position]
currentDisplay.isRed = adapterSeconds > 5
holder.itemView.apply {
val redColor = ContextCompat.getColor(context, R.color.red).toString()
val blackColor = ContextCompat.getColor(context, R.color.black).toString()
if (currentDisplay.isRed) {
holder.textView1.setTextColor(redColor.toInt())
holder.textView1.text = currentDisplay.rvSeconds.toString()
} else {
holder.textView1.setTextColor(blackColor.toInt())
holder.textView1.text = currentDisplay.rvSeconds.toString()
}
}
}
override fun getItemCount() = rvDisplay.size
}
Data Class:
data class Seconds(
var rvSeconds: Int,
var isRed: Boolean
)
when you call secondsList.add(addSeconds) then the data that is already inside secondsList should be updated too.
you could do something like
private var secondsList = generateSecondsList() // make this var
fun addSecondsToRecyclerView() {
val addSeconds =
Seconds(getDuration(), true)
secondsList.add(addSeconds)
if ( /* TODO check if time has passed */) {
secondsList = secondsList.map { it.isRed = true }
secondsAdapter.rvDisplay = secondsList // TODO also make rvDisplay a var
secondsAdapter.notifyDatasetChanged() // also need to tell rv to redraw the all views
} else {
secondsAdapter.notifyItemInserted(secondsList.size - 1)
}
}
that might work, but to be honest it looks bad... There is already a lot of logic inside Activity. Read about MVVM architecture and LiveData, there should be another class called ViewModel that would keep track of time and the data. Activity should be as simple as possible, because it has lifecycle, so if you rotate the screen, all your state will be lost.
Your code isn't really working because of this:
var adapterSeconds = MainActivity().getDuration()
override fun onBindViewHolder(holder: Adapter.AdapterViewHolder, position: Int) {
...
currentDisplay.isRed = adapterSeconds > 5
...
}
You're only setting adapterSeconds right there, so it never updates as time passes. I assume you want to know the moment 5 seconds has elapsed, and then update the RecyclerView at that moment - in that case you'll need some kind of timer task that will fire after 5 seconds, and can tell the adapter to display things as red. Let's deal with that first:
class Adapter( private val rvDisplay: MutableList ) : RecyclerView.Adapter<Adapter.AdapterViewHolder>() {
private var displayRed = false
set(value) {
field = value
// Refresh the display - the ItemChanged methods mean something about the items
// has changed, rather than a structural change in the list
// But you can use notifyDataSetChanged if you want (better to be specific though)
notifyItemRangeChanged(0, itemCount)
}
override fun onBindViewHolder(holder: Adapter.AdapterViewHolder, position: Int) {
if (displayRed) {
// show things as red - you shouldn't need to store that state in the items
// themselves, it's not about them - it's an overall display state, right?
} else {
// display as not red
}
}
So with that setter function, every time you update displayRed it'll refresh the display, which calls onBindViewHolder, which checks displayRed to see how to style things. It's better to put all this internal refreshing stuff inside the adapter - just pass it data and events, let it worry about what needs to happen internally and to the RecyclerView it's managing, y'know?
Now we have a thing we can set to control how the list looks, you just need a timer to change it. Lots of ways to do this - a CountdownTimer, a coroutine, but let's keep things simple for this example and just post a task to the thread's Looper. We can do that through any View instead of creating a Handler:
// in MainActivity
recyclerView.postDelayed({ secondsAdapter.displayRed = true }, 5000)
That's it! Using any view, post a delayed function that tells the adapter to display as red.
It might be more helpful to store that runnable as an object:
private val showRedTask = Runnable { secondsAdapter.displayRed = true }
...
recyclerView.postDelayed(showRedTask, 5000)
because then you can easily cancel it
recyclerView.removeCallbacks(showRedTask)
Hopefully that's enough for you to put some logic together to get what you want. Set displayRed = false to reset the styling, use removeCallbacks to cancel any running task, and postDelayed to start a new countdown. Not the only way to do it, but it's pretty neat!
I finally figured it out using a companion object in Main Activity with a boolean set to false. If the time exceeded 5 seconds, then it set to true.
The adapter was able to recognize the companion object and change the color of seconds to red if they exceeded 5.

How to show "Toast" after scrolling down a certain amount in Kotlin

I want to implement a feature in my app where it shows a Toast/message when a user scrolls down the recyclerview. For example when the user is scrolling down the screen, after they pass about 10 items a Toast pops up saying "Tap the home button to go back to the top"
How would I go about doing this?
I don't know if this would work, but you can try doing it in your adapter. like
when (position) {
10 -> Toast.makeText().show
}
or use an if statment.
Again, I don't know for sure if it would work, but I think so.
I think it's probably preferable to base it on distance scrolled instead of which item has appeared on screen most recently, so the threshold for when the message should be shown is not dependent on screen size. It's also best to keep this behavior out of the Adapter because of separation of concerns.
Here's a scroll listener you could use to do this behavior. I think the code is self-explanatory.
open class OnScrolledDownListener(
private val context: Context,
private val thresholdDp: Int,
var resetOnReturnToTop: Boolean = true
): RecyclerView.OnScrollListener() {
private var eventFired = false
private var y = 0
open fun onScrolledDown() {}
open fun onScrolledBackToTop() {}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
y += dy
val yDp = (y / context.resources.displayMetrics.density).roundToInt()
if (yDp >= thresholdDp && !eventFired) {
eventFired = true
onScrolledDown()
} else if (resetOnReturnToTop && yDp == 0 && eventFired) {
eventFired = false
onScrolledBackToTop()
}
}
}
You can subclass and override the two events for when it scrolls down at least a certain amount for the first time (onScrolledDown), and for when it is scrolled back to the top and resets itself (onScrolledBackToTop).
myRecyclerView.addOnScrollListener(object: OnScrolledDownListener(context, 120) {
override fun onScrolledDown() {
showMyMessage()
}
override fun onScrolledBackToTop() {
hideTheMessage()
}
})

Setters don't modify two-dimensional array

Kotlin. I want a button to display values from a two-dimensional ArrayList, and a second button to modify one of them. But the setters don't modify the two-dimensional ArrayList. We can see the values with the first button, and after modifying the values at index 2 (third) with the second button, the values don't change:
model.get(2).setDateStrs("03/03/20")
model.get(2).setHourStrs("10:27")
What's wrong?
ReModel.kt file:
package com.example.updatearraylist
class ReModel {
var dateStr:String = "12/31/2029"
var hourStr: String = "00:00"
fun getDateStrs(): String {
return dateStr
}
fun setDateStrs(dateStr: String) {
this.dateStr = dateStr
}
fun getHourStrs(): String {
return hourStr
}
fun setHourStrs(hourStr: String) {
this.hourStr = hourStr
}
}
MainActivity.kt file:
package com.example.updatearraylist
import android.R.attr
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import java.lang.reflect.Array.get
import java.util.*
import kotlin.collections.ArrayList
class MainActivity : AppCompatActivity() {
private var displayValueBtn: Button? = null
private var changeValueBtn: Button? = null
val model: ArrayList<ReModel>
get() {
val list = ArrayList<ReModel>()
for (i in 0..7) {
val model = ReModel()
model.setDateStrs("01/16/2020")
model.setHourStrs("01:08")
list.add(model)
}
return list
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
displayValueBtn =findViewById<Button>(R.id.displayValueBtn)
changeValueBtn=findViewById<Button>(R.id.changeValueBtn)
displayValueBtn!!.setOnClickListener {
for(i in 0..7){
Toast.makeText(this, "Value position "+i+" "+model.get(i).getDateStrs()+" "+
model.get(i).getHourStrs()
,Toast.LENGTH_SHORT).show()
}
}
changeValueBtn!!.setOnClickListener {
model.get(2).setDateStrs("03/03/20")
model.get(2).setHourStrs("10:27")
Toast.makeText(this,"List Modified",Toast.LENGTH_LONG).show()
}
}
}
The custom getter on model will be executed each time model is accessed so any changes to the array are overwritten. If you want to verify that use a single println in the custom getter and whatever you print will display multiple times.
By "custom getter" I mean the get() on model and the associated block of code.
One solution is to use lazy initialization instead of a custom getter so that model is initialized only once. Here's how that would look:
val model: ArrayList<ReModel> by lazy {
val list = ArrayList<ReModel>()
for (i in 0..7) {
val model = ReModel()
model.setDateStrs("01/16/2020")
model.setHourStrs("01:08")
list.add(model)
}
list
}
Note that the last line with just list on it returns the value of list. return is not allowed there.