Flutter MethodCall non-nil check in kotlin way - android

I tried to make a MethodCall type judgement function using kotlin, but it return a type mismatch for me, how should I fix with it?
import java.util.Objects
import io.flutter.plugin.common.MethodCall
class Utils private constructor() {
companion object {
fun getDoubleArgument(call: MethodCall, argument: String?): Double {
return try {
if (call.argument<Any>(argument) is Double) {
Objects.requireNonNull(call.argument<Double>(argument))!! // The call.argument return Double? type, but when I add !! assert, it report Unnecessary non-null assertion (!!).
} else if (call.argument<Any>(argument) is Long) {
val l = Objects.requireNonNull(call.argument<Long>(argument))
l!!.toDouble()
} else if ...
}

I don't use Flutter, so please excuse any errors.
You don't need Objects.requireNonNull() in Kotlin, because !! already does the exact same thing as requireNonNull(), except that it throws KotlinNullPointerException instead of NullPointerException. The reason it's not working is that Kotlin doesn't know that the Java method is guaranteed to return a non-null value.
class Utils private constructor() {
companion object {
fun getDoubleArgument(call: MethodCall, argument: String?): Double {
return try {
if (call.argument<Any>(argument) is Double) {
call.argument<Double>(argument)!!
} else if (call.argument<Any>(argument) is Long) {
call.argument<Long>(argument)!!.toDouble()
} else if ...
}
It would be better to get the argument one time and using that value. Then you can rely on Kotlin smart-casting to simplify the code:
class Utils private constructor() {
companion object {
fun getDoubleArgument(call: MethodCall, argument: String?): Double {
val arg = call.argument<Any>(argument)
return try {
if (arg is Double) {
arg
} else if (arg is Long) {
arg.toDouble()
} // ...
else if (arg == null) {
0.0
} else {
error("unsupported argument type")
}
}
You can use a when statement instead of a chain of else if to make it easier to read:
class Utils private constructor() {
companion object {
fun getDoubleArgument(call: MethodCall, argument: String?): Double {
val arg = call.argument<Any>(argument)
return try {
when (arg) {
is Double -> arg
is Long -> arg.toDouble()
// ...
null -> 0.0
else -> error("unsupported argument type")
}
}
}

Related

What the difference between observe and wrapper observeEvents (LiveData)?

There is a convenient wrapper that allows you to reduce the boilerplate when you work with LiveData - observeEvents.
open class Event<T>(value: T? = null) {
val liveData = MutableLiveData(value)
protected var hasBeenHandled = false
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled || liveData.value == null) {
null
} else {
hasBeenHandled = true
liveData.value
}
}
companion object {
fun <T> LifecycleOwner.observeEvents(event: Event<T>, body: (T?) -> Unit) {
event.liveData.observe(this) { body(event.getContentIfNotHandled()) }
}
}
}
class MutableEvent<T>(value: T? = null) : Event<T>(value) {
#MainThread
fun fireEvent(event: T) {
hasBeenHandled = false
liveData.value = event
}
#WorkerThread
fun postEvent(event: T) {
hasBeenHandled = false
liveData.postValue(event)
}
}
Next, we can see how to use it.
There is the following sealed class for specific events:
sealed class ProductEvent {
data class AddProduct(val data: SomeProduct) : ProductEvent()
data class RemoveProduct(val productId: String) : ProductEvent()
}
ViewModel code:
private val _productEvents = MutableEvent<ProductEvent>()
val productEvents = _productEvents
private fun addProduct() {
val product: SomeProduct = repository.getProduct()
_productEvents.fireEvent(ProductEvent.AddProduct(product)
}
Activity/Fragment code:
observeEvents(viewModel.productEvents) { event ->
event?.let {
when(event) {
is ProductEvent.AddProduct -> // add product
is ProductEvent.RemoveProduct-> // remove product
}
}
}
Everything works fine, but there is one thing.
For example, when we use registerForActivityResult:
private val result = registerForActivityResult(ActivityResultContracts.StartActivityForResult())
{ result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) {
result.data?.getIntExtra(SomeActivity.SOME_RESULT, 0)?.let {
// do work that will call ProductEvent
// viewModel.addProduct() - for example
}
}
}
When SomeActivity finishes and we return here, this code will run before LifecycleOwner will be active and because of that a subscriber will not be called.
There is a solution (lifecycleScope.launchWhenResumed), but the fact is that if we define our LiveData as usual:
// viewModel
private val _product = MutableLiveData<SomeProduct>()
val product = _product
// Activity/Fragment
viewModel.product.observe(lifecycleOwner) {}
then the subscriber will work as expected.
I would like to know what the difference. observeEvents is merely a wrapper that does the same thing, but for some reason works a little differently.

How to emit distinct values from MutableLiveData?

I observed that MutableLiveData triggers onChanged of an observer even if the same object instance is provided to its setValue method.
//Fragment#onCreateView - scenario1
val newValue = "newValue"
mutableLiveData.setValue(newValue) //triggers observer
mutableLiveData.setValue(newValue) //triggers observer
//Fragment#onCreateView - scenario2
val newValue = "newValue"
mutableLiveData.postValue(newValue) //triggers observer
mutableLiveData.postValue(newValue) //does not trigger observer
Is there a way to avoid an observer be notified twice if the same or an equivalent instance is provided to setValue()/postValue()
I tried extending MutableLiveData but that did not work. I could be missing something here
class DistinctLiveData<T> : MutableLiveData<T>() {
private var cached: T? = null
#Synchronized override fun setValue(value: T) {
if(value != cached) {
cached = value
super.setValue(value)
}
}
#Synchronized override fun postValue(value: T) {
if(value != cached) {
cached = value
super.postValue(value)
}
}
}
There is already in API : Transformations.distinctUntilChanged()
distinctUntilChanged
public static LiveData<X> distinctUntilChanged (LiveData<X> source)
Creates a new LiveData object does not emit a value until the source
LiveData value has been changed. The value is considered changed if
equals() yields false.
<<snip remainder>>
You can use the following magic trick to consume "items being the same":
fun <T> LiveData<T>.distinctUntilChanged(): LiveData<T> = MediatorLiveData<T>().also { mediator ->
mediator.addSource(this, object : Observer<T> {
private var isInitialized = false
private var previousValue: T? = null
override fun onChanged(newValue: T?) {
val wasInitialized = isInitialized
if (!isInitialized) {
isInitialized = true
}
if(!wasInitialized || newValue != previousValue) {
previousValue = newValue
mediator.postValue(newValue)
}
}
})
}
If you want to check referential equality, it's !==.
But it has since been added to Transformations.distinctUntilChanged.
If we talk about MutableLiveData, you can create a class and override setValue and then only call through super if new value != old value
class DistinctUntilChangedMutableLiveData<T> : MutableLiveData<T>() {
override fun setValue(value: T?) {
if (value != this.value) {
super.setValue(value)
}
}
}
In my case I have quite complex objects which I have to compare by some fields. For this I've changed EpicPandaForce's answer:
fun <T> LiveData<T>.distinctUntilChanged(compare: T?.(T?) -> Boolean = { this == it }): LiveData<T> = MediatorLiveData<T>().also { mediator ->
mediator.addSource(this) { newValue ->
if(!newValue.compare(value)) {
mediator.postValue(newValue)
}
}
}
By default it uses standard equals method, but if you need - you can change distinction logic

How can I generalize functions on an enum class in Kotlin?

How can I create a class which could be more reusable with enum classes, as I might have few more classes later on? My point is to make it more reusable, flexible and global for other usage.
enum class PaymentMethodType(val type: String) {
PAYPAL("Paypal"),
VISA("Visa"),
MASTERCARD("MasterCard"),
VISA_DEBIT("VISA Debit"),
LPQ_CREDIT("Lpq Credit");
companion object {
private val TAG: String = this::class.java.simpleName
fun fromString(name: String): PaymentMethodType? {
return getEnumFromString(PaymentMethodType::class.java, name)
}
private inline fun <reified T : Enum<T>> getEnumFromString(c: Class<T>?, string: String?): T? {
if (c != null && string != null) {
try {
return enumValueOf<T>(
string.trim()
.toUpperCase(Locale.getDefault()).replace(" ", "_")
)
} catch (e: IllegalArgumentException) {
Log.e(TAG, e.message)
}
}
return null
}
}
}
You can generalize your getEnumFromString function by creating an interface and having your companion object implementing it. An extension on this interface will let you call the function directly on the companion of your enum class.
This will do the trick:
interface EnumWithKey<T : Enum<T>, K> {
val T.key: K
}
/* The reified type parameter lets you call the function without explicitly
* passing the Class-object.
*/
inline fun <reified T : Enum<T>, K> EnumWithKey<T, K>.getByKey(key: K): T? {
return enumValues<T>().find { it.key == key }
}
Now you can create your PaymentMethodType like this:
enum class PaymentMethodType(val type: String) {
PAYPAL("Paypal"),
VISA("Visa"),
MASTERCARD("MasterCard"),
VISA_DEBIT("VISA Debit"),
LPQ_CREDIT("Lpq Credit");
companion object : EnumWithKey<PaymentMethodType, String> {
// Just define what the key is
override val PaymentMethodType.key
get() = type
}
}
And voila, now you can do this:
println(PaymentMethodType.getByKey("Paypal")) // Prints PAYPAL
The EnumWithKey interface can now be reused by just having the companion object of an enum implementing it.
Well? How about this code?
enum class PaymentMethodType(val type: String) {
PAYPAL("Paypal"),
VISA("Visa"),
MASTERCARD("MasterCard"),
VISA_DEBIT("VISA Debit"),
LPQ_CREDIT("Lpq Credit");
companion object {
private val TAG: String = PaymentMethodType::class.simpleName
fun fromString(name: String?): PaymentMethodType? {
val maybeType = PaymentMethodType.values().firstOrNull { it.type == name }
if (maybeType == null) {
Log.e(TAG, "No corresponding PaymentMethodType for $name")
}
return maybeType
}
}
}
Just made getEnumFromString method simpler like this way.
Moreover, if you want to make your PaymentMethodType more "reusable, flexible and global", add some abstract method onto your PaymentMethodType or consider using Sealed class in this case. We can guess that many payment methods require their own protocols, and implementing it by enum requires an externalised when or if-else branch to do so. For example, the code should be looks like this:
fun paymentProcessor(payment: PaymentMethodType): Boolean {
return when (payment) {
PAYPAL -> { processPaypalPayment() }
VISA -> { processVisaPayment() }
// ...
}
}
which is not bad unless numbers of payment methods are limited but not quite desirable. We can remove this insidious if or when keyword like this way(retaining enum class approach):
enum class PaymentMethodType(val type: String) {
PAYPAL("Paypal") {
override fun processPayment(): Boolean {
TODO("Not implemented.")
}
},
VISA("Visa") {
override fun processPayment(): Boolean {
TODO("Not implemented.")
}
},
// ... more types ...
;
abstract fun processPayment(): Boolean
// ...
}
With either approach, we can eliminate when keyword in paymentProcessor method I demonstrated like this:
fun paymentProcessor(payment: PaymentMethodType): Boolean {
return payment.processPayment()
}
I don't explain sealed class approach since the code is not much different compare to enum class approach in this case. The official document may help.
Hope this helps.
Get all enum values with PaymentMethodType.values(), then use find() to get the one you need:
fun fromString(type: String): PaymentMethodType? = PaymentMethodType.values().find { it.type.toLowerCase() == type.toLowerCase() }

Moshi adapter to skip bad objects in the List<T>

I use Moshi and I need to solve my problem with a buggy backend. Sometimes, when I request a list of objects, some of them don't contain mandatory fields. Of course, I can catch and process JsonDataException, but I want to skip these objects. How can I do it with Moshi?
Update
I have a couple of models for my task
#JsonClass(generateAdapter = true)
data class User(
val name: String,
val age: Int?
)
#JsonClass(generateAdapter = true)
data class UserList(val list: List<User>)
and buggy JSON
{
"list": [
{
"name": "John",
"age": 20
},
{
"age": 18
},
{
"name": "Jane",
"age": 21
}
]
}
as you can see, the second object has no mandatory name field and I want to skip it via Moshi adapter.
There's a gotcha in the solution that only catches and ignores after failure. If your element adapter stopped reading after an error, the reader might be in the middle of reading a nested object, for example, and then the next hasNext call will be called in the wrong place.
As Jesse mentioned, you can peek and skip the entire value.
class SkipBadElementsListAdapter(private val elementAdapter: JsonAdapter<Any?>) :
JsonAdapter<List<Any?>>() {
object Factory : JsonAdapter.Factory {
override fun create(type: Type, annotations: Set<Annotation>, moshi: Moshi): JsonAdapter<*>? {
if (annotations.isNotEmpty() || Types.getRawType(type) != List::class.java) {
return null
}
val elementType = Types.collectionElementType(type, List::class.java)
val elementAdapter = moshi.adapter<Any?>(elementType)
return SkipBadElementsListAdapter(elementAdapter)
}
}
override fun fromJson(reader: JsonReader): List<Any?>? {
val result = mutableListOf<Any?>()
reader.beginArray()
while (reader.hasNext()) {
try {
val peeked = reader.peekJson()
result += elementAdapter.fromJson(peeked)
} catch (ignored: JsonDataException) {
}
reader.skipValue()
}
reader.endArray()
return result
}
override fun toJson(writer: JsonWriter, value: List<Any?>?) {
if (value == null) {
throw NullPointerException("value was null! Wrap in .nullSafe() to write nullable values.")
}
writer.beginArray()
for (i in value.indices) {
elementAdapter.toJson(writer, value[i])
}
writer.endArray()
}
}
It seems I've found the answer
class SkipBadListObjectsAdapterFactory : JsonAdapter.Factory {
override fun create(type: Type, annotations: MutableSet<out Annotation>, moshi: Moshi): JsonAdapter<*>? {
return if (annotations.isEmpty() && Types.getRawType(type) == List::class.java) {
val elementType = Types.collectionElementType(type, List::class.java)
val elementAdapter = moshi.adapter<Any>(elementType)
SkipBadListObjectsAdapter(elementAdapter)
} else {
null
}
}
private class SkipBadListObjectsAdapter<T : Any>(private val elementAdapter: JsonAdapter<T>) :
JsonAdapter<List<T>>() {
override fun fromJson(reader: JsonReader): List<T>? {
val goodObjectsList = mutableListOf<T>()
reader.beginArray()
while (reader.hasNext()) {
try {
elementAdapter.fromJson(reader)?.let(goodObjectsList::add)
} catch (e: JsonDataException) {
// Skip bad element ;)
}
}
reader.endArray()
return goodObjectsList
}
override fun toJson(writer: JsonWriter, value: List<T>?) {
throw UnsupportedOperationException("SkipBadListObjectsAdapter is only used to deserialize objects")
}
}
}
Thank you "guys from the other topics" =)
You can find a working solution here:
https://github.com/square/moshi/issues/1288
Happy fixing :)

Kotlin lazy properties and values reset: a resettable lazy delegate

So I use kotlin for android, and when inflating views, I tend to do the following:
private val recyclerView by lazy { find<RecyclerView>(R.id.recyclerView) }
This method will work. However, there is a case in which it will bug the app. If this is a fragment, and the fragment goes to the backstack, onCreateView will be called again, and the view hierarchy of the fragment will recreated. Which means, the lazy initiated recyclerView will point out to an old view no longer existent.
A solution is like this:
private lateinit var recyclerView: RecyclerView
And initialise all the properties inside onCreateView.
My question is, is there any way to reset lazy properties so they can be initialised again? I like the fact initialisations are all done at the top of a class, helps to keep the code organised. The specific problem is found in this question: kotlin android fragment empty recycler view after back
Here is a quick version of a resettable lazy, it could be more elegant and needs double checked for thread safety, but this is basically the idea. You need something to manage (keep track) of the lazy delegates so you can call for reset, and then things that can be managed and reset. This wraps lazy() in these management classes.
Here is what your final class will look like, as an example:
class Something {
val lazyMgr = resettableManager()
val prop1: String by resettableLazy(lazyMgr) { ... }
val prop2: String by resettableLazy(lazyMgr) { ... }
val prop3: String by resettableLazy(lazyMgr) { ... }
}
Then to make the lazy's all go back to new values on next time they are accessed:
lazyMgr.reset() // prop1, prop2, and prop3 all will do new lazy values on next access
The implementation of the resettable lazy:
class ResettableLazyManager {
// we synchronize to make sure the timing of a reset() call and new inits do not collide
val managedDelegates = LinkedList<Resettable>()
fun register(managed: Resettable) {
synchronized (managedDelegates) {
managedDelegates.add(managed)
}
}
fun reset() {
synchronized (managedDelegates) {
managedDelegates.forEach { it.reset() }
managedDelegates.clear()
}
}
}
interface Resettable {
fun reset()
}
class ResettableLazy<PROPTYPE>(val manager: ResettableLazyManager, val init: ()->PROPTYPE): Resettable {
#Volatile var lazyHolder = makeInitBlock()
operator fun getValue(thisRef: Any?, property: KProperty<*>): PROPTYPE {
return lazyHolder.value
}
override fun reset() {
lazyHolder = makeInitBlock()
}
fun makeInitBlock(): Lazy<PROPTYPE> {
return lazy {
manager.register(this)
init()
}
}
}
fun <PROPTYPE> resettableLazy(manager: ResettableLazyManager, init: ()->PROPTYPE): ResettableLazy<PROPTYPE> {
return ResettableLazy(manager, init)
}
fun resettableManager(): ResettableLazyManager = ResettableLazyManager()
And some unit tests to be sure:
class Tester {
#Test fun testResetableLazy() {
class Something {
var seed = 1
val lazyMgr = resettableManager()
val x: String by resettableLazy(lazyMgr) { "x ${seed}" }
val y: String by resettableLazy(lazyMgr) { "y ${seed}" }
val z: String by resettableLazy(lazyMgr) { "z $x $y"}
}
val s = Something()
val x1 = s.x
val y1 = s.y
val z1 = s.z
assertEquals(x1, s.x)
assertEquals(y1, s.y)
assertEquals(z1, s.z)
s.seed++ // without reset nothing should change
assertTrue(x1 === s.x)
assertTrue(y1 === s.y)
assertTrue(z1 === s.z)
s.lazyMgr.reset()
s.seed++ // because of reset the values should change
val x2 = s.x
val y2 = s.y
val z2 = s.z
assertEquals(x2, s.x)
assertEquals(y2, s.y)
assertEquals(z2, s.z)
assertNotEquals(x1, x2)
assertNotEquals(y1, y2)
assertNotEquals(z1, z2)
s.seed++ // but without reset, nothing should change
assertTrue(x2 === s.x)
assertTrue(y2 === s.y)
assertTrue(z2 === s.z)
}
}
I find a convenient method:
import java.util.concurrent.atomic.AtomicReference
import kotlin.reflect.KProperty
fun <T> resetableLazy(initializer: () -> T) = ResetableDelegate(initializer)
class ResetableDelegate<T>(private val initializer: () -> T) {
private val lazyRef: AtomicReference<Lazy<T>> = AtomicReference(
lazy(
initializer
)
)
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
return lazyRef.get().getValue(thisRef, property)
}
fun reset() {
lazyRef.set(lazy(initializer))
}
}
test:
import org.junit.Assert
import org.junit.Test
class ResetableLazyData {
var changedData = 0
val delegate = resetableLazy { changedData }
val readOnlyData by delegate
}
class ResetableLazyTest {
#Test
fun testResetableLazy() {
val data = ResetableLazyData()
data.changedData = 1
Assert.assertEquals(data.changedData, data.readOnlyData)
data.changedData = 2
Assert.assertNotEquals(data.changedData, data.readOnlyData)
data.delegate.reset()
Assert.assertEquals(data.changedData, data.readOnlyData)
data.changedData = 3
Assert.assertNotEquals(data.changedData, data.readOnlyData)
}
}
I had the same task, and this is what I used:
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
class SingletonLazy<T : Any>(val initBlock: () -> T, val clazz: Class<T>) {
operator fun <R> provideDelegate(ref: R, prop: KProperty<*>): ReadOnlyProperty<R, T> = delegate()
#Suppress("UNCHECKED_CAST")
private fun <R> delegate(): ReadOnlyProperty<R, T> = object : ReadOnlyProperty<R, T> {
override fun getValue(thisRef: R, property: KProperty<*>): T {
val hash = clazz.hashCode()
val cached = singletonsCache[hash]
if (cached != null && cached.javaClass == clazz) return cached as T
return initBlock().apply { singletonsCache[hash] = this }
}
}
}
private val singletonsCache = HashMap<Int, Any>()
fun <T> clearSingleton(clazz: Class<T>) : Boolean {
val hash = clazz.hashCode()
val result = singletonsCache[hash]
if (result?.javaClass != clazz) return false
singletonsCache.remove(hash)
return true
}
inline fun <reified T : Any> singletonLazy(noinline block: () -> T): SingletonLazy<T>
= SingletonLazy(block, T::class.java)
usage:
val cat: Cat by singletonLazy { Cat() }
fun main(args: Array<String>) {
cat
println(clearSingleton(Cat::class.java))
cat // cat will be created one more time
println(singletonsCache.size)
}
class Cat {
init { println("creating cat") }
}
Of course, you may have you own caching strategies.
If you want something very simple, extends Lazy<T> and yet efficient in few lines of code, you could use this
class MutableLazy<T>(private val initializer: () -> T) : Lazy<T> {
private var cached: T? = null
override val value: T
get() {
if (cached.isNull()) {
cached = initializer()
}
#Suppress("UNCHECKED_CAST")
return cached as T
}
fun reset() {
cached = null
}
override fun isInitialized(): Boolean = cached != null
companion object {
fun <T> resettableLazy(value: () -> T) = MutableLazy(value)
}
}
Use it like this:
class MainActivity() {
val recyclerViewLazy = MutableLazy.resettable {
findViewById<RecyclerView>(R.id.recyclerView)
}
val recyclerView by recyclerViewLazy
// And later on
override onCreate(savedInstanceState: Bundle?) {
recyclerViewLazy.reset() /** On next get of the recyclerView, it would be updated*/
}
}
Borrowed partly from
lazy(LazyThreadSafetyMode.NONE) { }
provided in the stlib
you can try this
fun <P, T> renewableLazy(initializer: (P) -> T): ReadWriteProperty<P, T> =
RenewableSynchronizedLazyWithThisImpl({ t, _ ->
initializer.invoke(t)
})
fun <P, T> renewableLazy(initializer: (P, KProperty<*>) -> T): ReadWriteProperty<P, T> =
RenewableSynchronizedLazyWithThisImpl(initializer)
class RenewableSynchronizedLazyWithThisImpl<in T, V>(
val initializer: (T, KProperty<*>) -> V,
private val lock: Any = {}
) : ReadWriteProperty<T, V> {
#Volatile
private var _value: Any? = null
override fun getValue(thisRef: T, property: KProperty<*>): V {
val _v1 = _value
if (_v1 !== null) {
#Suppress("UNCHECKED_CAST")
return _v1 as V
}
return synchronized(lock) {
val _v2 = _value
if (_v2 !== null) {
#Suppress("UNCHECKED_CAST") (_v2 as V)
} else {
val typedValue = initializer(thisRef, property)
_value = typedValue
typedValue
}
}
}
override fun setValue(thisRef: T, property: KProperty<*>, value: V) {
// 不论设置何值,都会被重置为空
synchronized(lock) {
_value = null
}
}
}

Categories

Resources