I'm trying to find out how to achieve the combination of "if let + cast" in kotlin:
in swift:
if let user = getUser() as? User {
// user is not nil and is an instance of User
}
I saw some documentation but they say nothing regarding this combination
https://medium.com/#adinugroho/unwrapping-sort-of-optional-variable-in-kotlin-9bfb640dc709
https://kotlinlang.org/docs/reference/null-safety.html
One option is to use a safe cast operator + safe call + let:
(getUser() as? User)?.let { user ->
...
}
Another would be to use a smart cast inside the lambda passed to let:
getUser().let { user ->
if (user is User) {
...
}
}
But maybe the most readable would be to just introduce a variable and use a smart cast right there:
val user = getUser()
if (user is User) {
...
}
Kotlin can automatically figure out whether a value is nil or not in the current scope based on regular if statements with no need for special syntax.
val user = getUser()
if (user != null) {
// user is known to the compiler here to be non-null
}
It works the other way around too
val user = getUser()
if (user == null) {
return
}
// in this scope, the compiler knows that user is not-null
// so there's no need for any extra checks
user.something
In Kotlin you can use the let:
val user = getUser()?.let { it as? User } ?: return
This solution is closest to guard but it may be useful.
In Kotlin you can use:
(getUser() as? User)?.let { user ->
// user is not null and is an instance of User
}
as? is a 'safe' cast operator that returns null instead of throwing an error on failure.
What about this one?
val user = getUser() ?: return
Related
I wrote function to return value from database, it works because i assign value to state and recomposition, but that is not proper way to interact with database.
fun getUser():State<User?>{
val id = sharedPrefs?.getString("uId", "")
id?.let {
if (it != "")
runBlocking {
CoroutineScope(Dispatchers.IO).launch {
repository?.getUser(it)?.let {
currentUser.value = it
Log.v("user_1",""+it)
}
}
}
}
Log.v("user_2",""+currentUser.value)
return currentUser
}
I thought runBlocking will make it work, but user_1 contains value and user_2 is null.
So changes are visible only in coroutine which seems to be performed after return.
I would be grateful if you can provide any good resource about coroutines.
The correct way to do this is to use a suspend function and withContext instead of launch -- there are several other things I'd clean up in your code (eliminate almost all the ?.let), but this should do:
suspend fun getUser():State<User?>{
val id = sharedPrefs?.getString("uId", "")
id?.let {
if (it != "")
withContext(Dispatchers.IO) {
repository?.getUser(it)?.let {
currentUser.value = it
Log.v("user_1",""+it)
}
}
}
Log.v("user_2",""+currentUser.value)
return currentUser
}
Basically I have a screen, and there are a few EditTexts and a Button.
Users have to fill in all fields otherwise the Button is disabled.
I am using DataBinding to achieve this. Below is my code in the viewmodel.
val isNextEnabled = MediatorLiveData<Boolean>()
isNextEnabled.apply {
addSource(field1LiveData) {
isNextEnabled.value =
it != null
&& field2LiveData.value != null
&& field3LiveData.value != null
}
addSource(field2LiveData) {
isNextEnabled.value =
it != null
&& field1LiveData.value != null
&& field3LiveData.value != null
}
addSource(field3LiveData) {
isNextEnabled.value =
it != null
&& field2LiveData.value != null
&& field1LiveData.value != null
}
}
In the xml
<Button
android:enabled="#{viewmodel.isNextEnabled}"
.
.
.
</Button>
Everything works fine as expected. But the logic above looks cumbersome. What if I have more EditText ? The code would be painful to write/maintain.
Is there any way I can simplify it?
Ultimately you have a UseCase/Logic where you decide when the next button is enabled.
I think you should separate the logic into useCases where it makes sense.
E.g.
// update these when they change in the UI for e.g.
val field1Flow: Flow<Boolean> = flow { ... }
val field2Flow: Flow<Boolean> = flow { ... }
val nextButtonState = combine(field1Flow, field2Flow) { f1, f2 ->
f1 && f2
}.collect { state ->
// use your state.
}
Now... if you need special logic and not just two-boolean algebra here, you can always extract it into use-cases that return more flows.
Or map it or various operations you could do:
E.g.
class YourUseCase() {
operator fun invoke(field1: Boolean, field2: Boolean) {
// Your Logic
return field1 && field2
}
}
// And now...
val _nextButtonState = combine(field1Flow, field2Flow) { f1, f2 ->
YourUseCase(f1, f2)
}
val _uiState = _nextButtonState.transformLatest {
emit(it) // you could add a when(it) { } and do more stuff here
}
// And if you don't want to change your UI to use flows, you can expose this as live data
val uiState = _uiState.asLiveData()
Keep in mind this is Pseudo-code written on SO.. not even Notepad ;)
I hope that makes a bit of sense. The idea is to separate the bits into use-cases (that you can ultimately test in isolation) and to have a flow of data. When buttons change state, the fieldNFlow emits the values and this triggers the whole chain for you.
If you have the latest Coroutines (2.4.0+) you can use the new operators to avoid using LiveData, but overall, I'd try to think in that direction.
Lastly, your liveData code with a mediator is not bad, I'd at the very least, extract the "logic" into 3 different useCases so it's not all together in a series of if/else statements.
A word of caution: I haven't used Databinding in over 3(?) years, I'm personally not a fan of it so I cannot tell you if it would cause a problem with this approach.
I have created a composable called ResolveAuth. ResolveAuth is the first screen when user opens the app after Splash. All it does is check whether an email is present in Datastore or not. If yes redirect to main screen and if not then redirect to tutorial screen
Here is my composable and viewmodel code
#Composable
fun ResolveAuth(resolveAuthViewModel: ResolveAuthViewModel, navController: NavController) {
Scaffold(content = {
ProgressBar()
when {
resolveAuthViewModel.userEmail.value != "" -> {
navController.navigate(Screen.Main.route) {
popUpTo(0)
}
resolveAuthViewModel.userEmail.value = null
}
resolveAuthViewModel.userEmail.value == "" -> {
navController.navigate(Screen.Tutorial.route) {
popUpTo(0)
}
resolveAuthViewModel.userEmail.value = null
}
}
})
}
#HiltViewModel
class ResolveAuthViewModel #Inject constructor(
private val dataStoreManager: DataStoreManager): ViewModel(){
val userEmail = MutableLiveData<String>()
init {
viewModelScope.launch{
val job = async {dataStoreManager.email.first()}
val email = job.await()
if(email != ""){
userEmail.value = email
}
}
}
}
But I keep getting an exception saying
java.lang.IllegalStateException: You cannot access the NavBackStackEntry's ViewModels until it is added to the NavController's back stack (i.e., the Lifecycle of the NavBackStackEntry reaches the CREATED state).
I am using below jetpack lib for navigation
implementation("androidx.navigation:navigation-compose:2.4.0-rc01")
There is no issue in my Main and Tutorial screen as I tried to run them separately and it works fine.
Easily resolvable, just add this when call to a Side-Effect instead.
LaunchedEffect(Unit){
while(!isNavStackReady) // Hold execution while the NavStack populates.
delay(16) // Keeps the resources free for other threads.
when {
resolveAuthViewModel.userEmail.value != "" -> {
navController.navigate(Screen.Main.route) {
popUpTo(0)
}
resolveAuthViewModel.userEmail.value = null
}
resolveAuthViewModel.userEmail.value == "" -> {
navController.navigate(Screen.Tutorial.route) {
popUpTo(0)
}
resolveAuthViewModel.userEmail.value = null
}
}
}
Here, the call to navigate is made only after the currentBackStackEntry has been completely filled, so it yields no error. The original error occurred since you were calling navigate before the concerned composable was even made available to the nav stack.
As for how to update the isNavStackReady variable to reflect the correct state of the navStack, it is fairly simple. Create the variable at a top-level declaration, such that only the required components may access it. May as well throw it inside a viewModel if you please. Set the default value of the var to false, for obvious reasons. Here's the update mechanism.
#Composable
fun StartDestination(){
isNavStackReady = true
}
That's it, that's really it. If you could successfully navigate to your start destination that you define in the nav graph, it means the navStack has likely been populated well. Hence, you just update this variable here, and the LaunchedEffect block up there will respond to this update, and the while loop that's been holding execution off, will finally break. It will then call the navigate on the appropriate destination route. Remember, however, that the isNavStackReady variable, for this mechanism to work, needs to be a state-holder, i.e., initialised with mutableStateOf(false). Using delegates, of course, is completely fine (personally encouraged).
Now, all this is fine, but actually, it's not quite the right implementation. You see, this entire thing is taken care of completely internally by the navigation APIs for us, but it breaks because we are trying to do its job, and we suck at it.
We are creating an intermediate route to land on, at the start of the app, and from there, immediately navigating to another screen based on calculations. So, all we want is to open the app at a desired page, that is, start the navigator on a desired page when it is first created. We have a handy parameter called startDestination, just for that.
Hence, the ideal, simple, beautiful solution would be to just
startDestination = when {
resolveAuthViewModel.userEmail.value != "" -> {
navController.navigate(Screen.Main.route) {
popUpTo(0)
}
resolveAuthViewModel.userEmail.value = null
}
resolveAuthViewModel.userEmail.value == "" -> {
navController.navigate(Screen.Tutorial.route) {
popUpTo(0)
}
resolveAuthViewModel.userEmail.value = null
}
}
in your NavBuilder's arguments. Tiniest silliest logical flaw, that so many people couldn't get. It's intriguing to think how the human mind works...
Happy New Year,
I have this function which gets a parameter and first checks for its value. if it was null then gets its value from the result of fisrtFun() which returns a Single< String>.
after that either that parameter was null or not, it returns the result of secondFun() which gets that parameter as input
fun getUserEvents(id: String?): Single<String> {
return if (userId == null) {
firstFun().flatMap { id->
secondFun(id)
}
} else {
secondFun(id)
}
}
But as you see I used if-else blocks and have written secondFun() multiple times
so I tried to make it cleaner
fun getUserEvents(id: String?): Single<String> {
val id = userId ?: fisrtFun().blockingGet()!!
return secondFun(id)
}
I want to know if there is a better way to achieve this functionality without using blockingGet() to avoid blocking the thread
Something like this should work:
fun getUserEvents(id: String?): Single<String> {
val idSingle = if (userId == null) firstFun() else Single.just(id)
return idSingle.flatMap { id ->
secondFun(id)
}
}
In Kotlin, it's common to use let to execute code if an object (let receiver) isn't null, as an alternative to an if != null check, as in the following:
val nullable: String? = "anything"
nullable?.let {
println(it)
}
In what other situations does it make sense to make use of let?
FYI, let is part of Kotlin's stdlib and is defined as follows:
#kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
I've seen let used to scope nested variables (ignoring let's return):
some.nested.foo.bar.let { bar ->
doSomething(bar)
doMoreStuff(bar)
}
It can be nice since it replaces the need to define a local variable, but I'm not sure how useful it actually is.
Could also be done with apply, though the readability is a bit worse, (this instead of bar in the scope).
let is also useful when you work with var variables which are nullable.
For instance, we have this code
fun doSomething(value: String) {
}
class A {
var boo: String? = null
fun b() {
if (boo != null) {
doSomething(boo)
}
}
}
There, we have compile-time error inside if block because boo can be changed outside. To fix that we should either create a val variable
class A {
var boo: String? = null
fun b() {
val b = boo
if (b != null) {
doSomething(b)
}
}
}
or use let
class A {
var boo: String? = null
fun b() {
boo?.let {
doSomething(it)
}
}
}
scoping capabilities
The stdlib function let enables you to confine a variable's scope to a specific block of code. Shown here:
compile("a:b:c")
"io.vertx:vertx".let { v->
compile("$v-lang-kotlin:$vertxVersion")
compile("$v-lang-kotlin-coroutines:$vertxVersion")
}
compile("x:y:z")
We could alternatively define a val v: String = "io.vertx:vertx" which might make the code more readable since that's what we'd do in Java for example. Yet, that val would be accessible throughout the whole method. We just want to have it available for the compile units with vertx involved, thus we confine the variable holding io.vertx:vertx to the let block.
Informations on the different scoping function alternatives can be found in this thread.
you can use let in let in let by naming
someMethodCall()?.let{ nameOne ->
// ....
// some code here
// ....
val resultCall = nameOne
someMethod2Call()?.let { -> nameTwo
// ...
val myVariable = nameTwo + resultCall
// ...
}
}