Kotlin: return a value on button click - android

I'm just starting out on learning Kotlin, and I'm currently making a simple Quiz application for Android. The user can choose an answer with four buttons which are stored in an array. The program contains a functions which is supposed to check if the correct button is clicked and return a corresponding boolean:
fun checkAnswer (solution: Int): Boolean {
for (z in answerButtons.indices) {
answerButtons[z].setOnClickListener{
return z == solution
}
}
}
Now I know that this return doesn't work, but I just can't find a way to return a value depending on which button is clicked. If anyone could help me here, I'd be very grateful. Thanks!

So when you call setOnClickListener, the Kotlin compiler is really abstracting away some important details. What is really happening is this:
setOnClickListener(object: View.OnClickListener {
override fun onClick(v: View?) {
doAThing()
}
})
This is a SAM constructor. But, as you can see, the return type of onClick is Unit, and it doesn't make sense to return from an anonymous object either. It would help to have more context as to why you've structured your code the way you have, but here's a potential solution to your problem:
// in onCreate
for (btn in answerButtons) {
btn.setOnClickListener {
if (btn.text == solution) {
doTheThingWhenCorrectAnswer()
} else {
doTheThingWhenIncorrectAnswer()
}
}
}
If this were a real application, I would additionally suggest pushing the logic for checking answers into the model layer to maintain strong separation of concerns.

Related

Android fragment safe args

Can somebody explain to me how it's works?
From (MainFragment) tap on FAB to create new fragment(HabitEditorScreenFragment) through navigation component. Applying to it Parcelable argument. In that fragment i'm going in new fragment(EditColorFragment) with new argument type of Int. In that fragment:
saving new int in sharable viewModel
i'm going back with findNavController().navigateUp()
and now is strange things. If i enter something in editfields that values will be in safeArgs value. But i haven't save it in there anywhere!
Update:
It happend riht in time when i type somthing in edit fields.
But:
private fun setDoAfterTextChanged() = with(binding) {
fhesHabitNameInput.doAfterTextChanged {
viewModel.editorHabit.name = fhesHabitNameInput.text.toString()
viewModel.canWeSave() //just cheking errors
checkName() // checking specific field
}
fhesHabitDescriptionInput.doAfterTextChanged {
viewModel.editorHabit.description = fhesHabitDescriptionInput.text.toString()
viewModel.canWeSave()
checkDescription()
}
fhesHabitFrequencyInput.doAfterTextChanged {
viewModel.editorHabit.frequency = fhesHabitFrequencyInput.text.toString()
viewModel.canWeSave()
checkFrequency()
}
}
Here's the code
Ok, i'm figure it out what's happening. Dumb me. :D
viewModel.editorHabit = args.habitCharacteristics
this is changing link on viewModel.EditorHabit not copying info from args. Really stupid :D Sorry for that.

How to offer a transformed liveData from DB on Room, if initialization/update of the DB might be needed?

Background
I'm creating some SDK library, and I want to offer some liveData as a returned object for a function, that will allow to monitor data on the DB.
The problem
I don't want to reveal the real objects from the DB and their fields (like the ID), and so I wanted to use a transformation of them.
So, suppose I have this liveData from the DB:
val dbLiveData = Database.getInstance(context).getSomeDao().getAllAsLiveData()
What I did to get the liveData to provide outside, is:
val resultLiveData: LiveData<List<SomeClass>> = Transformations.map(
dbLiveData) { data ->
data.map { SomeClass(it) }
}
This works very well.
However, the problem is that the first line (to get dbLiveData) should work on a background thread, as the DB might need to initialize/update, and yet the Transformations.map part is supposed to be on the UI thread (including the mapping itself, sadly).
What I've tried
This lead me to this kind of ugly solution, of having a listener to a live data, to be run on the UI thread:
#UiThread
fun getAsLiveData(someContext: Context,listener: OnLiveDataReadyListener) {
val context = someContext.applicationContext ?: someContext
val handler = Handler(Looper.getMainLooper())
Executors.storageExecutor.execute {
val dbLiveData = Database.getInstance(context).getSomeDao().getAllAsLiveData()
handler.post {
val resultLiveData: LiveData<List<SomeClass>> = Transformations.map(
dbLiveData) { data ->
data.map { SomeClass(it) }
}
listener.onLiveDataReadyListener(resultLiveData)
}
}
}
Note: I use simple threading solution because it's an SDK, so I wanted to avoid importing libraries when possible. Plus it's quite a simple case anyway.
The question
Is there some way to offer the transformed live data on the UI thread even when it's all not prepared yet, without any listener ?
Meaning some kind of "lazy" initialization of the transformed live data. One that only when some observer is active, it will initialize/update the DB and start the real fetching&conversion (both in the background thread, of course).
The Problem
You are an SDK that has no UX/UI, or no context to derive Lifecycle.
You need to offer some data, but in an asynchronous way because it's data you need to fetch from the source.
You also need time to initialize your own internal dependencies.
You don't want to expose your Database objects/internal models to the outside world.
Your Solution
You have your data as LiveData directly from your Source (in this particular, albeit irrelevant case, from Room Database).
What you COULD do
Use Coroutines, it's the preferred documented way these days (and smaller than a beast like RxJava).
Don't offer a List<TransformedData>. Instead have a state:
sealed class SomeClassState {
object NotReady : SomeClassState()
data class DataFetchedSuccessfully(val data: List<TransformedData>): SomeClassState()
// add other states if/as you see fit, e.g.: "Loading" "Error" Etc.
}
Then Expose your LiveData differently:
private val _state: MutableLiveData<SomeClassState> = MutableLiveData(SomeClassState.NotReady) // init with a default value
val observeState(): LiveData<SomeClassState) = _state
Now, whoever is consuming the data, can observe it with their own lifecycle.
Then, you can proceed to have your fetch public method:
Somewhere in your SomeClassRepository (where you have your DB), accept a Dispatcher (or a CoroutineScope):
suspend fun fetchSomeClassThingy(val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default) {
return withContext(defaultDispatcher) {
// Notify you're fetching...
_state.postValue(SomeClassState.Loading)
// get your DB or initialize it (should probably be injected in an already working state, but doesn't matter)
val db = ...
//fetch the data and transform at will
val result = db.dao().doesntmatter().what().you().do()
// Finally, post it.
_state.postValue(SomeClassState.DataFetchedSuccessfully(result))
}
}
What else I would do.
The fact that the data is coming from a Database is or should be absolutely irrelevant.
I would not return LiveData from Room directly (I find that a very bad decision on Google that goes against their own architecture that if anything, gives you the ability to shoot your own feet).
I would look at exposing a flow which allows you to emit values N times.
Last but not least, I do recommend you spend 15 minutes reading the recently (2021) published by Google Coroutines Best Practices, as it will give you an insight you may not have (I certainly didn't do some of those).
Notice I have not involved a single ViewModel, this is all for a lower layer of the architecture onion. By injecting (via param or DI) the Dispatcher, you facilitate testing this (by later in the test using a Testdispatcher), also doesn't make any assumption on the Threading, nor imposes any restriction; it's also a suspend function, so you have that covered there.
Hope this gives you a new perspective. Good luck!
OK I got it as such:
#UiThread
fun getSavedReportsLiveData(someContext: Context): LiveData<List<SomeClass>> {
val context = someContext.applicationContext ?: someContext
val dbLiveData =
LibraryDatabase.getInstance(context).getSomeDao().getAllAsLiveData()
val result = MediatorLiveData<List<SomeClass>>()
result.addSource(dbLiveData) { list ->
Executors.storageExecutor.execute {
result.postValue(list.map { SomeClass(it) })
}
}
return result
}
internal object Executors {
/**used only for things that are related to storage on the device, including DB */
val storageExecutor: ExecutorService = ForkJoinPool(1)
}
The way I've found this solution is actually via a very similar question (here), which I think it's based on the code of Transformations.map() :
#MainThread
public static <X, Y> LiveData<Y> map(
#NonNull LiveData<X> source,
#NonNull final Function<X, Y> mapFunction) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(source, new Observer<X>() {
#Override
public void onChanged(#Nullable X x) {
result.setValue(mapFunction.apply(x));
}
});
return result;
}
Do note though, that if you have migration code (from other DBs) on Room, it might be a problem as this should be on a background thread.
For this I have no idea how to solve, other than trying to do the migrations as soon as possible, or use the callback of "onCreate" (docs here) of the DB somehow, but sadly you won't have a reference to your class though. Instead you will get a reference to SupportSQLiteDatabase, so you might need to do a lot of manual migrations...

transformations.switchMap works only once or possibly not at all

I am looking for some help with transformations.switchMap. I somehow can't get it to work and start to wonder if I actually understand things correctly.
Based on my understanding the first parameter is like a trigger, whenever it's values changes the, second parameter/function will be mapped/called to return a new/modified liveData object.
In my case I have:
#MainActivity
viewModel.repository.items.observe(requireActivity(), Observer { weight -> weight?.let { adapter!!.setItems(it) } })
#ViewModel
lateinit var items: LiveData<List<Weight>> # Used in my RecyclerView
var filterChanged = MutableLiveData<Long>(0L) # The "trigger" value
override fun init() {
super.init()
items = Transformations.switchMap(filterChanged) {repository.getItems(0L, filterEnd)}
}
repository.getItems() returns a live data object filtered by filterEnd (SQL ...where timestamp <= filterEnd), with timestamp being a date in numerical representation.
This works! But only once, during initialisation of the Fragment. Once the fragment has initialised I can change the filterChanged trigger variable as often as I want,
the repository.getItems() function does not get called a second time (applying an updated value for filterEnd).
It requires start/stop of a secondary activity (settings or about) which will recreate my viewModel to update the filter value.
I saw several ways of crafting the Transformations.switchMap statement, using lambdas, one parameter or two, etc., but I either couldn't get them to compile or they didn't do what I expected.
Now, have I understood correctly how this is supposed to work? Any idea what I might be doing wrong?
Edit: Added the repository section to clarify things:
#Repository
items = getItems(filterStart, filterEnd) # This is where I enter from the ViewModel
// This function redirects to the actual dao functions,
fun getItems(filterStart: Long, filterEnd: Long): LiveData<List<Weight>> {
Log.d("Repository ", "getItems: $filterStart, $filterEnd")
if ((filterStart != 0L) && (filterEnd == 0L)) return dao.getItemsGreaterThan(filterStart)
else if ((filterStart == 0L) && (filterEnd != 0L)) return dao.getItemsLessThan(filterEnd)
else if ((filterStart != 0L) && (filterEnd != 0L)) return dao.getItemsRange(filterStart, filterEnd)
else return dao.getItems()
}
At one point I simplified things and called the dao functions directly, like so:
items = dao.getItemsLessThan(filterEnd)
Even bypassed the repository allt ogether and called the respective dao function directly from the ViewModel, but that didn't make any difference either.
And then there are the corresponding dao functions, like the below:
#Query("SELECT * from weight WHERE timestamp >=:start ORDER BY timestamp DESC")
abstract fun getItemsGreaterThan(start: Long): LiveData<List<Weight>>

Setting the result from Room LiveData to a textview

I have a two queries which return two long values. I am setting these two long values to be displayed in individual text views. Finally I have a third text view which displays the combined value of both longs. I am having a problem getting the combined total to show as its setting the value before the livedata is returned.
Below is a snippet of the code
private void getData() {
mViewModelReframing.totalWorkouts(pkUserId).observe(getViewLifecycleOwner(), new Observer<List<ModelStatsTotalWorkouts>>() {
#Override
public void onChanged(List<ModelStatsTotalWorkouts> modelStatsTotalWorkouts) {
for (ModelStatsTotalWorkouts list : modelStatsTotalWorkouts) {
totalReframeWorkouts = list.getTotalWorkouts();
}
if (totalReframeWorkouts == 0) {
tvTotalReframes.setText(0 + getString(R.string.workouts_empty));
} else {
tvTotalReframes.setText("" + totalReframeWorkouts);
}
}
});
mViewModelCheckIn.totalWorkouts(pkUserId).observe(getViewLifecycleOwner(), new Observer<List<ModelStatsTotalWorkouts>>() {
#Override
public void onChanged(List<ModelStatsTotalWorkouts> tableCheckIns) {
for (ModelStatsTotalWorkouts list : tableCheckIns) {
totalCheckInWorkouts = list.getTotalWorkouts();
}
tvTotalCheckIns.setText("" + totalCheckInWorkouts);
// Combine both longs together for a combined total.
totalWorkouts = totalReframeWorkouts + totalCheckInWorkouts;
tvTotalWorkouts.setText("" + totalWorkouts);
}
});
}
Is there a better way to write the logic to achieve the desired result without the issue of the livedata not being returned fast enough?
Whenever you use independent Reactive streams like this (LiveData, RxJava, etc) you are going to have race conditions. You need to make explicit the dependencies for an action to happen - in this case your ability to update the UI in the way that you want had dependencies on BOTH APIs returning. This is the RxJava equivalent of zip. A few tips:
Consider using only a single Viewmodel for a view. The viewmodel should really be preparing data for your view specificially. In this case, it should really be that singular ViewModel that handles combining this data before passing it to your vew at all.
Barring that, since you've chosen LiveData here, you can do what you want by using a MediatorLiveData. Essentially, it acts as a composite stream source that depends on whichever other LiveData streams you add to it as described by that article. In this way, you can explicitly wait for all the needed values to arrive before you try to update the UI.
I solved the question by using this method:
public LiveData<List<ModelStatsTotalWorkouts>> totalWorkoutsCombined(long userId) {
LiveData liveData1 = database.getUsersDao().totalReframeWorkouts(userId);
LiveData liveData2 = database.getUsersDao().totalCheckInWorkouts(userId);
MediatorLiveData liveDataMerger = new MediatorLiveData<>();
liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value));
liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value));
return liveDataMerger;
}

Which one should I use between if (data!=null) and data?.let in kotlin?

The Code A is common usage in java.
I havn't understanded completely the key let of Kotin. Which one should I use between Code A and Code B in kotlin? Thanks!
Code A
if (data!=null){
initAndBindAdapter()
mCustomAdapter.setSelectedIndex(data.getIntExtra("index",-1))
}
Code B
data?.let {
initAndBindAdapter()
mCustomAdapter.setSelectedIndex(it.getIntExtra("index",-1))
}
And more, which one should I choose between Code C and Code D in kotlin if the fun do_NoNeed_data_par doesn't need data parameter ?
Code C
if (data!=null){
do_NoNeed_data_par()
}
Code D
data?.let {
do_NoNeed_data_par()
}
I (personal opinion) think it's a good idea to use simple, regular null checks where you can, although the ?.let method has been listed under the main Kotlin Idioms page of the documentation (which is open for the community to contribute) - so basically, this will be up to your personal preferences of which one is more readable.
The more interesting question is what are the differences, and when you can use each: the main difference is that let holds on to the value of the variable as it was when the let call on it started, and any subsequent uses of it within the let block will reference that same value. If you use a simple null check with if, your variable's value might be changed while the body of the if block is being executed.
So for example, this won't compile, because x can be accessed by multiple threads, and it might be non-null when you read its value first for the null check, but it might become null by the time you read it again for the println parameter - this would be unsafe:
class Foo {
var x: Int? = null
fun useX() {
if (x != null) {
println(x + 10) // (...) 'x' is a mutable property that could have been changed by this time
}
}
}
However, a let will work in the same situation, because it will use whatever the initial value of x had all throughout its execution, even if the x property in the class gets reassigned in the meantime:
class Foo {
var x: Int? = null
fun useX() {
x?.let {
println(it + 10)
}
}
}
You can think of the ?.let statement above of basically performing this, creating a temporary copy of your variable:
fun useX() {
val _x = x
if (_x != null) {
println(_x + 10)
}
}
Operating on this copy is safe, because even if the x property changes its value, this _x copy will either stay null for this entire function, or it's non-null and safe to use.
"should" is opinionated. It all depends on your preference.
If you prefer more functional style code then .let is your answer. If you prefer more procedural code then == null is your answer.
Sometimes, using let() can be a concise alternative for if. But you have to use it with sound judgment in order to avoid unreadable “train wrecks”. Nevertheless, I really want you to consider using let().
val order: Order? = findOrder()
if (order != null){
dun(order.customer)
}
With let(), there is no need for an extra variable. So we get along with one expression.
findOrder()?.let { dun(it.customer) }

Categories

Resources