Using an array of null instead of an optional - android

I was wondering why an array of nulls of size 1 instead an optional:
https://github.com/android/architecture-components-samples/blob/master/GithubBrowserSample/app/src/test-common/java/com/android/example/github/util/LiveDataTestUtil.kt#L27
Original code:
#Throws(InterruptedException::class)
fun <T> getValue(liveData: LiveData<T>): T {
val data = arrayOfNulls<Any>(1)
val latch = CountDownLatch(1)
val observer = object : Observer<T> {
override fun onChanged(o: T?) {
data[0] = o
latch.countDown()
liveData.removeObserver(this)
}
}
liveData.observeForever(observer)
latch.await(2, TimeUnit.SECONDS)
return data[0] as T
}
why not using:
#Throws(InterruptedException::class)
fun <T> getValue(liveData: LiveData<T>): T {
var data: T? = null
val latch = CountDownLatch(1)
val observer = object : Observer<T> {
override fun onChanged(o: T?) {
data = o
latch.countDown()
liveData.removeObserver(this)
}
}
liveData.observeForever(observer)
latch.await(2, TimeUnit.SECONDS)
return data!!
}
Is it better to use an array than an optional object there? why?
Or it's just the same?
Thanks

Looking at the project history, the file used to be Java, but was translated directly into Kotlin - the original Java version is here, and you can see they just converted it directly to Kotlin.
While the version you gave makes more sense in Kotlin, there is no such capability in Java, because local variables you use in an anonymous class or lambda have to be final. That is why a single element array is used instead. But if they had written it originally in Kotlin (or if they were to refactor it), they would almost certainly do it the way you did.

Related

Proper way to update LiveData from the Model?

The "proper" way to update views with Android seems to be LiveData. But I can't determine the "proper" way to connect that to a model. Most of the documentation I have seen shows connecting to Room which returns a LiveData object. But (assuming I am not using Room), returning a LiveData object (which is "lifecycle aware", so specific to the activity/view framework of Android) in my model seems to me to violate the separation of concerns?
Here is an example with Activity...
class MainActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_activity);
val viewModel = ViewModelProvider(this).get(UserViewModel::class.java)
val nameText = findViewById<TextView>(R.id.nameTextBox)
viewModel.getName().observe(this, { name ->
nameText.value = name
})
}
}
And ViewModel...
class UserViewModel(): ViewModel() {
private val name: MutableLiveData<String> = MutableLiveData()
fun getName() : LiveData<String> {
return name
}
}
But how do I then connect that to my Model without putting a "lifecycle aware" object that is designed for a specific framework in my model (LiveData)...
class UserModel {
val uid
var name
fun queryUserInfo() {
/* API query here ... */
val request = JSONObjectRequest( ...
{ response ->
if( response.name != this.name ) {
this.name = response.name
/* Trigger LiveData update here somehow??? */
}
}
)
}
}
I am thinking I can maybe put an Observable object in my model and then use that to trigger the update of the LiveData in my ViewModel. But don't find any places where anyone else says that is the "right" way of doing it. Or, can I instantiate the LiveData object in the ViewModel from an Observable object in my model?
Or am I just thinking about this wrong or am I missing something?
This is from official documentation. Check comments in code...
UserModel should remain clean
class UserModel {
private val name: String,
private val lastName: String
}
Create repository to catch data from network
class UserRepository {
private val webservice: Webservice = TODO()
fun getUser(userId: String): LiveData<UserModel > {
val data = MutableLiveData<UserModel>() //Livedata that you observe
//you can get the data from api as you want, but it is important that you
//update the LiveDate that you will observe from the ViewModel
//and the same principle is in the relation ViewModel <=> Fragment
webservice.getUser(userId).enqueue(object : Callback<UserModel > {
override fun onResponse(call: Call<User>, response: Response<UserModel >) {
data.value = response.body()
}
// Error case is left out for brevity.
override fun onFailure(call: Call<UserModel >, t: Throwable) {
TODO()
}
})
return data //you will observe this from ViewModel
}
}
The following picture should explain to you what everything looks like
For more details check this:
https://developer.android.com/jetpack/guide
viewmodels-and-livedata-patterns-antipatterns

Why can't I assign a value to LiveData<Boolean>?

The Code A can work well.
I think Code B can work well too, but in fact , it failed, why ?
The error information is listed below.
Val cannot be reassigned
Code A
val displayCheckBox : LiveData<Boolean> = _displayCheckBox
fun switchCheckBoxShowStatus(){
_displayCheckBox.value?.let {
_displayCheckBox.value = !it
}
}
Code B
val displayCheckBox : LiveData<Boolean> = _displayCheckBox
fun switchCheckBoxShowStatus(){
_displayCheckBox.value?.let {
it = !it
}
}
it is passed as a local variable in lambda. So basically you are trying to modify a Val . Which will not compile.
inline fun <T, R> T.let(block: (T) -> R): R
As the Scoping function let defines it will call the block(Which is being passed as lambda) with this value as its argument. So its will be a method argument a val . So you can not reassign it.

Clear retrofit result with MVVM when fragment back

In my ViewModel I have two MutableLiveData for the response of my webservice :
val getFindByCategorySuccess: MutableLiveData<List<DigitalService>> by lazy {
MutableLiveData<List<DigitalService>>()
}
val getFindByCategoryError: MutableLiveData<String> by lazy {
MutableLiveData<String>()
}
and this method for the request :
fun requestFindByCategory(categoryId: String){
viewModelScope.launch {
when (val retrofitPost = digitalServicesRemoteRepository.getFindByCategoryRequest(categoryId)) {
is ApiResult.Success -> getFindByCategorySuccess.postValue(retrofitPost.data)
is ApiResult.Error -> getFindByCategoryError.postValue(retrofitPost.exception)
}
}
}
It's working fine using it in my Fragment class :
viewModel.getFindByCategorySuccess.observe(viewLifecycleOwner, { digitalServices ->
logD("I have a good response from the webservice; luanch an other fragment now!")
})
The problem is if I go to an other fragment in my observable (using findNavController().navigate(action)). If I go back to the previous fragment, I go automatically to the nextFragment because the observable is called again.
So I'm looking for solutions...
Maybe clearing all my viewmodel when I go back to my fragment ?
Maybe clearing only getFindByCategorySuccess and getFindByCategoryError ?
Maybe an other solution? I think my architecture is not good. What do you think about it ?
By default, a livedata will emit to its current state (the value that exist on it) for any new observer that subscribes to it.
Answering your question, you might try the operator distincUntilChanged transformation, which, according to the documentation:
Creates a new LiveData object that does not emit a value until the source LiveData value has been changed. The value is considered changed if equals() yields false.
But, this showcases a problem with your snippet, and a bad practice that is common when using livedata, you shouldn't expose mutable live data to your observers. Instead, you should expose a non-mutable version of them.
In your case, in my opinion, your view model should look like the following:
private val getFindByCategorySuccess by lazy {
MutableLiveData<List<DigitalService>>()
}
private val getFindByCategoryError by lazy {
MutableLiveData<String>()
}
val onFindByCategorySuccess: LiveData<List<DigitalService>
get() = getFindByCategorySuccess.distincUntilChanged()
val onFindCategoryError: LiveData<List<String>
get() = getFindByCategoryrRror.distincUntilChanged()
And your observers would subscribe as follows:
ExampleFragment
fun setupObservers() {
viewModel.onFindByCategorySuccess.observe(viewLifecycleOwner) { // Do stuff }
}
I hope it helps
I found a solution to my problem using this class :
class SingleLiveEvent<T> : MutableLiveData<T>() {
private val mPending = AtomicBoolean(false)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
super.observe(owner, { t ->
if (mPending.compareAndSet(true, false))
observer.onChanged(t)
})
}
override fun setValue(t: T?) {
mPending.set(true)
super.setValue(t)
}
}
Like this :
var getFindByCategorySuccess: SingleLiveEvent<List<DigitalService>> = SingleLiveEvent()
var getFindByCategoryError: SingleLiveEvent<String> = SingleLiveEvent()

Type mismatch. Required: Observer<in Int!> Found:?

I want to Observe live data change in ViewModel and want to change another live data so I am using Mediatorlivedata, I don't know how to observe it in ViewModel, I am getting the compile-time error
Type mismatch. Required: Observer Found: ?
class CheckmeasureViewModel(private val repository: UserRepository) : ViewModel() {
var estimateFinancialyear: ArrayList<FinYear> = ArrayList()
var asset = arrayListOf("Select")
var estimate = arrayListOf("Select")
var appPref: AppPref
var estimateyearpos = MutableLiveData<Int>()
var mediatorLiveData: MediatorLiveData<Int> = MediatorLiveData()
init {
appPref = AppPref.getInstance()!!
estimateFinancialyear.add(FinYear(0, "Select"))
estimateFinancialyear.addAll(repository.getFinYears())
estimateyearpos.observeForever(object : Observer<in Int> {
fun onChanged(#Nullable integer: Int?) { //Do something with "integer"
}
})
}
}
You Shouldn't observe a live data on the viewModel, try adding it as a source to a mediatorLiveData and observing it directly on the view:
val mediatorLiveData: MediatorLiveData<Int> = MediatorLiveData().apply{
addSource(estimateyearpos) { /*Do something with "integer" */}
}
or even (if you don't need it to be mutable)
val liveData = Transformations.map(estimateyearpos) { /*Do something with "integer" */}
Both of this options will observe the source live data and apply the given function to it, but you still need to observe it on an Activity or a fragment to properly get the values.

Cannot use 'T' as reified type parameter

/**
this "T::class.java" report an error :Cannot use 'T' as reified type parameter. Use a class instead!
so how can i fix it or what can i do to realize this way?please.
**/
see the next kotlin code
data class PostHttpResultBean<T>(private var errno:Int,private var error:String,private var data:String):IHttpResultEntity<T>{
override val errorCode: Int
get() = errno
override val errorMessage: String
get() = error
override val isSuccess: Boolean
get() = errno==0
override val result:T
get() = RSAUtil.dataDecrypt(RSAUtil.getKeyPassword(), data,T::class.java)!!
class RSAUtil {
companion object {
fun <T> dataDecrypt(password: String, data: String, java: Class<T>): T? {
val content = Base64.decode(data.toByteArray(), Base64.NO_WRAP)
try {
var deString = decrypt(content, password)
if (!deString.isEmpty()){
val first = deString.substring(0, deString.lastIndexOf(":") + 1)
deString = "$first$deString}"
return Gson().fromJson(deString,java)
}
return null
} catch (e: Exception) {
e.printStackTrace()
}
return null
}
}
}
You should change dataDecrypt like that:
inline fun <reified T> dataDecrypt(password: String, data: String): T? {
...
try {
...
if (!deString.isEmpty()){
...
return Gson().fromJson(deString, T::class.java)
}
...
}
}
And on the call site the T type will be inferred from result:
override val result:T
get() = RSAUtil.dataDecrypt(RSAUtil.getKeyPassword(), data)!!
You can read more about inline functions and reified types here and I strongly recommend to do so. I would also point out that your code is ill-formatted, it is advised to use ?: instead of !! in nullability checks and companion objects are discouraged in Kotlin, you could define functions outside of class and use (or import) them as if they were static.

Categories

Resources