How can I make it better with Kotlin isNullOrEMpty() - android

fun updatePath(query: String?){
this.query = query
}
fun build(): String {
if (!query.isNullOrEmpty()) {
val encodedQuery = encode(query)
}
}
encode() accepts only not null String variable.
but here it still complains that query that I am passing?
I was expecting using isNullOrEmpty() should solve this problem.

You can use kotlin Scope function with Null Safety to avoid such warnings.
Like this -
fun build(): String {
query?.let {
if (it.isNotEmpty()) {
val encodedQuery = encode(query)
}
}
}
EDIT: Thanks #Tenfour04 for notifying this, we should used instance inside block.
fun build(): String {
query?.let { it ->
if (it.isNotEmpty()) {
val encodedQuery = encode(**it**)
}
}
}

I would use smartcast or possibly takeIf to avoid nesting.
query?.takeIf { it.isNotEmpty() }?.let {
encode(it)
}

If encode accepts any non-null string (including empty string), then you could do the following.
fun build(): String {
...
val encodedQuery = query?.let(::encode)
...
}

It is not possible for the compiler to smartcast, as your query is a var, therefore it might change. You could do the following:
val query = query
Before the if
This shadows the name query. You can still use the query property through this.query

Related

Jetpack Compose: Room returns null for list of items

I am trying to get list of todos from database with livedata however, while debugging it always shows null for value. I have provided my files below.
My Dao:
#Query("SELECT * FROM todo_table WHERE IIF(:isCompleted IS NULL, 1, isCompleted = :isCompleted)")
fun getTodos(isCompleted: Boolean?): LiveData<List<Todo>>
My ViewModel:
private var _allTodoList = MutableLiveData<List<Todo>>()
var allTodoList: LiveData<List<Todo>> = _allTodoList
init {
viewModelScope.launch(Dispatchers.IO) {
val list = todoRepository.getTodos(null)
_allTodoList.postValue(list.value)
}
}
fun onFilterClick(todoType: Constants.TodoType) {
when (todoType) {
Constants.TodoType.ALL -> {
viewModelScope.launch(Dispatchers.IO) {
val list = todoRepository.getTodos(null)
_allTodoList.postValue(list.value)
}
}
Constants.TodoType.COMPLETED -> {
viewModelScope.launch(Dispatchers.IO) {
val list = todoRepository.getTodos(true)
_allTodoList.postValue(list.value)
}
}
Constants.TodoType.INCOMPLETE -> {
viewModelScope.launch(Dispatchers.IO) {
val list = todoRepository.getTodos(false)
_allTodoList.postValue(list.value)
}
}
}
}
My MainActivity:
val allTodoList = viewModel.allTodoList.observeAsState()
allTodoList.value?.run {//value is always null
if (!isNullOrEmpty()) {
...
} else {
...
}
}
While debugging I found that allTodoList.value is always null however, when I manually run same query in app inspection I the get the desired results.
You can simplify your code, see if it works.
ViewModel only needs this:
val allTodoList: LiveData<List<Todo>> = todoRepository.getTodos(null)
MainActivity:
val allTodoList by viewModel.allTodoList.observeAsState()
if (!allTodoList.isNullOrEmpty()) {
...
} else {
...
}
You are not observing the LiveData you get from Room.
YourDao.getTodos() and LiveData.getValue() are not suspend functions, so you get the current value, which is null because Room has not yet fetched the values from SQLite.
A possible solution would be to set the todo type as a live data itself and use a switchMap transformation in the ViewModel :
private val todoType = MutableLiveData<Constants.TodoType>(Constants.TodoType.ALL)
val allTodoList: LiveData<List<Todo>> = androidx.lifecycle.Transformations.switchMap(todoType) { newType ->
val typeAsBoolean = when(newType) {
Constants.TodoType.ALL -> null
Constants.TodoType.COMPLETED -> true
Constants.TodoType.INCOMPLETE -> false
else -> throw IllegalArgumentException("Not a possible value")
}
// create the new wrapped LiveData
// the transformation takes care of subscribing to it
// (and unsubscribing to the old one)
todoRepository.getTodos(typeAsBoolean)
}
fun onFilterClick(todoType: Constants.TodoType) {
// triggers the transformation
todoType.setValue(todoType)
}
This is in fact the exact use case demonstrated in the reference doc

Why isn't my Preferences DataStore value being set?

My understanding of AndroidX DataStore is the operations are supposed to be thread-safe and transactional. But I'm setting a value then immediately reading it, and the value has not been updated. What am I doing wrong? This shouldn't be possible should it?
Here are my "get" and "set" functions.
fun getValue(keyStr: String): String
{
val key = stringPreferencesKey(keyStr)
val value = runBlocking {
context.dataStore.data.map { it[key] ?: "" }
}
return runBlocking { value.first() }
}
fun setValue(keyStr: String, valueStr: String) {
val key = stringPreferencesKey(keyStr)
runBlocking {
context.dataStore.edit { preferences -> preferences[key] = valueStr }
}
}
And here is how they call them in my Application's CREATE method.
setValue("TEST", "testing")
val test = getValue("TEST")
After the "get" call, test=="".

Coroutines with sealed class

My project has a lot of operations that must be performed one after another. I was using listeners, but I found this tutorial Kotlin coroutines on Android and I wanted to change my sever call with better readable code. But I think I am missing something. The below code always return an error from getTime1() function:
suspend fun getTimeFromServer1() :ResultServer<Long> {
val userId = SharedPrefsHelper.getClientId()
return withContext(Dispatchers.IO) {
val call: Call<ResponseFromServer>? = userId?.let { apiInterface.getTime(it) }
(call?.execute()?.body())?.run {
val time:Long? = this.data?.time
time?.let {
Timber.tag("xxx").e("time received it ${it}")// I am getting the right result here
ResultServer.Success(it)
}
Timber.tag("xxx").e("time received ${time}")
}
ResultServer.Error(Exception("Cannot get time"))
}
}
fun getTime1() {
GlobalScope.launch {
when (val expr: ResultServer<Long> = NetworkLayer.getTimeFromServer1()) {
is ResultServer.Success<Long> -> Timber.tag("xxx").e("time is ${expr.data}")
is ResultServer.Error -> Timber.tag("xxx").e("time Error") //I am always get here
}}
}
}
But if I am using listeners (getTime()) everything works perfectly:
suspend fun getTimeFromServer(savingFinishedListener: SavingFinishedListener<Long>) {
val userId = SharedPrefsHelper.getClientId()
withContext(Dispatchers.IO) {
val call: Call<ResponseFromServer>? = userId?.let { apiInterface.getTime(it) }
(call?.execute()?.body())?.run {
val time:Long? = this.data?.time
time?.let {
Timber.tag("xxx").e("time received it ${it}")
savingFinishedListener.onSuccess(it)
}
}
savingFinishedListener.onSuccess(null)
}
}
fun getTime() {
GlobalScope.launch {
NetworkLayer.getTimeFromServer(object:SavingFinishedListener<Long>{
override fun onSuccess(t: Long?) {
t?.let {
Timber.tag("xxx").e("time here $it") //I am getting the right result
}
}
})
}
}
Thanks in advance for any help.
The last line of a lambda is implicitly the return value of that lambda. Since you don't have any explicit return statements in your withContext lambda, its last line:
ResultServer.Error(Exception("Cannot get time"))
means that it always returns this Error. You can put return#withContext right before your ResultServer.Success(it) to make that line of code also return from the lambda.
Side note: don't use GlobalScope.

RxJava Flowable.zip never returns a value

I have a code in my repository which has to call two endpoints. I have used Flowable.zip() but it doesn't seem to return a value. The Call doesn't fail even if there is no network available.
fun fetchRateRemote(): Flowable<ResultWrapper<List<RateModel>>> {
return Flowable.zip<Flowable<CurrenciesDTO>, Flowable<RateDTO>, ResultWrapper<List<RateModel>>>(
{
apiEndpoints.fetchCurrencies(key)
}, {
apiEndpoints.fetchRate(key)
}, { t1, t2 ->
val rateList = mutableListOf<RateModel>()
t2.subscribe { rate->
for((k,v) in rate.quotes ){
val currency = k.removeRange(0,3)
t1.subscribe {cur->
val currencyName = cur.currencies[currency]
if (currencyName != null) {
rateList.add(RateModel("$currencyName ($currency)", v.toString()))
}
}
}
}
ResultWrapper.Success(rateList)
}).subscribeOn(Schedulers.io())
}
I use a wrapper to mimic state and this is what I do in my viewmodel.
private fun fetchRates(){
disposable.add(repository.fetchRateRemote()
.startWith(ResultWrapper.Loading)
.onErrorReturn {
ResultWrapper.Error(it)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : DisposableSubscriber<ResultWrapper<List<RateModel>>>() {
override fun onComplete() {}
override fun onNext(rate: ResultWrapper<List<RateModel>>) {
rates.postValue(rate)
}
override fun onError(error: Throwable) {
error.printStackTrace()
}
})
)
}
I then observe rate in my activity via LiveData. The wrapper or the observation isn't the issue. It works with other calls, I do not know why the zip call doesn't work. I'm fairly new to RxJava so If I didn't implement something correctly in my repository please help correct me.
Okay! I made a lot of mistakes with the code in the repository above but I managed to fix it. Here's the solution. The Type arguments for the zip method was wrong! I didn't call the BiFunction argument properly too.
fun fetchRateRemote(): Flowable<ResultWrapper<List<RateModel>>> {
return Flowable.zip<CurrenciesDTO, RateDTO, ResultWrapper<List<RateModel>>>(
apiEndpoints.fetchCurrencies(key), apiEndpoints.fetchRate(key), BiFunction { t1, t2 ->
val rateList = mutableListOf<RateModel>()
for((k,v) in t2.quotes ){
val currencyCode = k.removeRange(0,3)
val currencyName = t1.currencies[currencyCode]
if (currencyName != null) {
rateList.add(RateModel("$currencyName ($currencyCode)", v.toString()))
}
}
ResultWrapper.Success(rateList)
}).subscribeOn(Schedulers.io())
}

How to check if kotlin attribute is private in android lint?

I am writing a custom android lint to help to check if the private attributes match naming convention.
I used the test cases to verify my implementation. I used a method called isPrivateOrParameterInPrivateMethod() to check if it is private or not but it seems return true everytime I run it.
And I cannot find any documentation about this method (org.jetbrains.kotlin.asJava.classesisPrivateOrParameterInPrivateMethod). If I used it incorrectly, I would like to know.
Appreciate any comment or advice
class PrivateVariableMPrefixDetector : Detector(), Detector.UastScanner {
override fun getApplicableUastTypes() = listOf<Class<out UElement>>(UVariable::class.java)
override fun createUastHandler(context: JavaContext) =
NamingPatternHandler(context)
class NamingPatternHandler(private val context: JavaContext) : UElementHandler() {
override fun visitVariable(node: UVariable) {
node.takeIf { it.isPrivateOrParameterInPrivateMethod() }
?.takeUnless { node.name?.first()?.equals('m') ?: false }
?.run {
process(node, node)
}
}
private fun process(scope: UElement, declaration: PsiNamedElement) {
context.report(
ISSUE_PRIVATE_VAR_PREFIX_WITH_M,
scope,
context.getNameLocation(scope),
"${declaration.name} is not named with prefix m"
)
}
}
}
Test Case
#Test
fun should_not_warn_when_public_variable_is_not_stated_with_m_prefix() {
TestLintTask.lint()
.files(
TestFiles.kt(
"""
class Foo {
val binding
}
"""
).indented()
)
.issues(ISSUE_PRIVATE_VAR_PREFIX_WITH_M)
.run()
.expectClean()
}
Updated on 13/12/2020
There is a class JavaEvaluator inside the JavaContext, and I found some useful method to check the explicit modifier for the variable which suits my cases
class MyHandler(private val context: JavaContext) : UElementHandler() {
override fun visitField(node: UField) {
val isConstant = node.isFinal && node.isStatic
val isEnumConstant = node is UEnumConstant
if (!isConstant && !isEnumConstant) {
node.takeIf {
context.evaluator.hasModifiers(node, KtTokens.PRIVATE_KEYWORD)
}?.run {
process(node, node)
}
}
}
}
Outdated
After putting an effort on it, I found the following solution works. Although I still dun quite understand the meaning of node.sourcePsi, i will make it work first. Appreciate any advice or suggestion
node.takeIf { node.sourcePsi?.text?.startsWith("private") ?: false }
?.takeUnless { node.name.first() == 'm' && node.name.getOrNull(1)?.isUpperCase() ?: false }
?.run {
process(node, node)
}

Categories

Resources