RealmList with #Parcelize annotation - android

I'm trying to use the new #Parcelize annotation added with Kotlin 1.1.4 with a Realm objet containing a RealmList attribute.
#Parcelize
#RealmClass
open class Garage(
var name: String? = null,
var cars: RealmList<Car> = RealmList()
) : Parcelable, RealmModel
As RealmList is not supported by the annotation and assuming that #Parcelize is there specially to avoid creating methods what would be the solution to support RealmList ?
Thanks in advance

EDIT:
Using the power of Type Parcelers and #WriteWith annotation, it is possible to create a RealmList type parceler that can handle this scenario for you:
With the following code:
fun <T> Parcel.readRealmList(clazz: Class<T>): RealmList<T>?
where T : RealmModel,
T : Parcelable = when {
readInt() > 0 -> RealmList<T>().also { list ->
repeat(readInt()) {
list.add(readParcelable(clazz.classLoader))
}
}
else -> null
}
fun <T> Parcel.writeRealmList(realmList: RealmList<T>?, clazz: Class<T>)
where T : RealmModel,
T : Parcelable {
writeInt(when {
realmList == null -> 0
else -> 1
})
if (realmList != null) {
writeInt(realmList.size)
for (t in realmList) {
writeParcelable(t, 0)
}
}
}
You can define an interface like this:
interface RealmListParceler<T>: Parceler<RealmList<T>?> where T: RealmModel, T: Parcelable {
override fun create(parcel: Parcel): RealmList<T>? = parcel.readRealmList(clazz)
override fun RealmList<T>?.write(parcel: Parcel, flags: Int) {
parcel.writeRealmList(this, clazz)
}
val clazz : Class<T>
}
Where you'll need to create a specific parceler for the RealmList<Car> like this:
object CarRealmListParceler: RealmListParceler<Car> {
override val clazz: Class<Car>
get() = Car::class.java
}
but with that, now you can do the following:
#Parcelize
#RealmClass
open class Garage(
var name: String? = null,
var cars: #WriteWith<CarRealmListParceler> RealmList<Car> = RealmList()
) : Parcelable, RealmModel
And
#Parcelize
#RealmClass
open class Car(
..
): RealmModel, Parcelable {
...
}
This way you don't need to manually write the Parceler logic.
ORIGINAL ANSWER:
Following should work:
#Parcelize
open class Garage: RealmObject(), Parcelable {
var name: String? = null
var cars: RealmList<Car>? = null
companion object : Parceler<Garage> {
override fun Garage.write(parcel: Parcel, flags: Int) {
parcel.writeNullableString(name)
parcel.writeRealmList(cars)
}
override fun create(parcel: Parcel): Garage = Garage().apply {
name = parcel.readNullableString()
cars = parcel.readRealmList()
}
}
}
As long as you also add following extension functions:
inline fun <reified T> Parcel.writeRealmList(realmList: RealmList<T>?)
where T : RealmModel,
T : Parcelable {
writeInt(when {
realmList == null -> 0
else -> 1
})
if (realmList != null) {
writeInt(realmList.size)
for (t in realmList) {
writeParcelable(t, 0)
}
}
}
inline fun <reified T> Parcel.readRealmList(): RealmList<T>?
where T : RealmModel,
T : Parcelable = when {
readInt() > 0 -> RealmList<T>().also { list ->
repeat(readInt()) {
list.add(readParcelable(T::class.java.classLoader))
}
}
else -> null
}
and also
fun Parcel.writeNullableString(string: String?) {
writeInt(when {
string == null -> 0
else -> 1
})
if (string != null) {
writeString(string)
}
}
fun Parcel.readNullableString(): String? = when {
readInt() > 0 -> readString()
else -> null
}

Related

Android - Custom NavType for List<Enum<*>>

I am trying to create a custom NavType for Enum with below code:
class EnumListType<T : Enum<T>> : NavType<List<T>>(true) {
#Suppress("TYPE_MISMATCH_WARNING")
override fun get(bundle: Bundle, key: String): List<T> {
val found = bundle.getStringArrayList(key)
return found?.map { enumValueOf(it) } ?: emptyList()
}
#Suppress("TYPE_MISMATCH_WARNING")
override fun parseValue(value: String): List<T> {
return value.substring(1, value.length - 1)
.split(',')
.map { enum -> java.lang.Enum.valueOf(T::class.java, enum) }
}
override fun put(bundle: Bundle, key: String, value: List<T>) {
bundle.putStringArrayList(key, values.map { it.name } as ArrayList<String>)
}
}
Both for get and parseValue, my code fails to compile with below error:
Cannot use 'T' as reified type parameter. Use a class instead.
Is it even possible to achieve a solution for this? If yes, what's wrong with the above code and how to fix it up?
class CustomType<T : Parcelable> : NavType<T>(isNullableAllowed = false) {
override fun get(bundle: Bundle, key: String): T? = bundle.getParcelable(key)
override fun parseValue(value: String): T {
val type: Type = object : TypeToken<Class<T>>() {}.type
return Gson().fromJson(value, type)
}
override fun put(bundle: Bundle, key: String, value: T) {
bundle.putParcelable(key, value)
}
}

How to Pass a List of Objects from activity A to activity B in Kotlin?

I have 2 Activities, I want to pass an arraylist of an Object and show it on a ListView
Activity A:
btnGuardar.setOnClickListener{
if(edtNombre.text.toString().equals("") ||
edtApellido.text.toString().equals("") ||
edtFecha.text.toString().equals("")){
Toast.makeText(this#Main2Activity, "Debes llenar todos los campos!", Toast.LENGTH_SHORT).show()
}else{
var estadoSel : String
estadoSel = estado.onItemSelectedListener.toString()
var per = Persona(edtNombre.text.toString(),edtApellido.text.toString(),estadoSel,edtFecha.text.toString())
personas.add(per)
val intent = Intent(this#Main2Activity,Main3Activity::class.java)
//intent.putExtra("Personas", personas as Serializable)
intent.putParcelableArrayListExtra("Personas", ArrayList(personas))
edtFecha.text = null
edtApellido.text = null
edtNombre.text = null
estado.setSelection(0)
Toast.makeText(this#Main2Activity, "Registro Guardado", Toast.LENGTH_SHORT).show()
}
}
Activity B:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main3)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
//val intent = Intent()
val bundle = getIntent().extras
var arr = bundle?.getParcelableArrayList<Persona>("Personas")!!
//var arr : ArrayList<Persona> = (ArrayList<Persona>())intent.getSerializableExtra("Personas")
//intent.getSerializableExtra("Personas")
//arr = intent.extras!!.get("Personas")
val adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, arr)
listaPersonas.adapter = adapter
}
But I'm getting a Null Pointer Exception
In your app's build.gradle under android scope add this and sync gradle -
androidExtensions {
experimental = true
}
Then make your object class parceble as follows -
#Parcelize
data class Persona() : Parcelable {
}
Now you can send and receive Parcelable list by intent.
Update : #Parcelize is no more experimental now, follow this answer for updated implementation https://stackoverflow.com/a/64925204/9854554
In your activity B to get your list try like the following
var arr = this.getIntent().getParcelableArrayListExtra<Parcelable>("Personas")
and make sure your Persona is extend Parcelable like below.
class Persona() : Parcelable {
// ....
#Override
public void writeToParcel(Parcel dest, int flags) {
//....
}
private void readFromParcel(Parcel in) {
//....
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Creator<Persona> {
override fun createFromParcel(parcel: Parcel): Persona{
return Persona(parcel)
}
override fun newArray(size: Int): Array<Persona?> {
return arrayOfNulls(size)
}
}
}
In your, Activity A send array list with
intent.putParcelableArrayListExtra("Personas", personas)
In your, Activity B get array list with
intent.getParcelableArrayListExtra("Personas")
In your app's build.gradle inside android tag add this and sync with gradle -
androidExtensions {
experimental = true
}
Data Class
#Parcelize data class Persona(
val name: String,
) : Parcelable
To pass List of Data via intent implement Parcelable interface on Model class.
Person Model Class:
import android.os.Parcel
import android.os.Parcelable
data class Person(val name: String?, val age: Int) : Parcelable {
constructor(source: Parcel) : this(
source.readString(),
source.readInt()
)
override fun describeContents() = 0
override fun writeToParcel(dest: Parcel, flags: Int) = with(dest) {
writeString(name)
writeInt(age)
}
companion object {
#JvmField
val CREATOR: Parcelable.Creator<Person> = object : Parcelable.Creator<Person> {
override fun createFromParcel(source: Parcel): Person = Person(source)
override fun newArray(size: Int): Array<Person?> = arrayOfNulls(size)
}
}
}
MainActivity:
class MainActivity : AppCompatActivity() {
var personList = arrayListOf<Person>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
personList.add(Person("Person1", 31))
personList.add(Person("Person2", 32))
personList.add(Person("Person3", 33))
var intent = Intent(this, BActivity::class.java)
intent.putParcelableArrayListExtra("personList", personList)
Handler().postDelayed( {startActivity(intent)}, 2000)
}
}
BActivity:
class BActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_b)
val extras = intent.extras
val personList = extras?.getParcelableArrayList<Person>("personList")
if (personList != null) {
personList.forEach { Log.i("BActivity", "Name: ${it.name} Age: ${it.age}" ) }
}else{
Log.i("BActivity", "PersonList in null");
}
}
}
Step 1. add this in your app build.gradle inside android scope.
androidExtensions {
experimental = true
}
Step 2: Make object class implement Parcelable
#Parcelize
class Persona():Parcelable {
}
Step 3. In activity A send data via intent:
val intent = Intent(this, B::class.java)
intent.putParcelableArrayListExtra("list", personaArrayList)
startActivity(intent)
Step 4: In activity B for recieving data:
val list = intent.getParcelableArrayListExtra<Persona>("list")

Serialize sealed class with Moshi

The following will produce an IllegalArgumentException because you "Cannot serialize abstract class"
sealed class Animal {
data class Dog(val isGoodBoy: Boolean) : Animal()
data class Cat(val remainingLives: Int) : Animal()
}
private val moshi = Moshi.Builder()
.build()
#Test
fun test() {
val animal: Animal = Animal.Dog(true)
println(moshi.adapter(Animal::class.java).toJson(animal))
}
I have tried solving this using a custom adapter, but the only solution I could figure out involves explicitly writing all of the property names for each subclass. e.g:
class AnimalAdapter {
#ToJson
fun toJson(jsonWriter: JsonWriter, animal: Animal) {
jsonWriter.beginObject()
jsonWriter.name("type")
when (animal) {
is Animal.Dog -> jsonWriter.value("dog")
is Animal.Cat -> jsonWriter.value("cat")
}
jsonWriter.name("properties").beginObject()
when (animal) {
is Animal.Dog -> jsonWriter.name("isGoodBoy").value(animal.isGoodBoy)
is Animal.Cat -> jsonWriter.name("remainingLives").value(animal.remainingLives)
}
jsonWriter.endObject().endObject()
}
....
}
Ultimately I'm looking to produce JSON that looks like this:
{
"type" : "cat",
"properties" : {
"remainingLives" : 6
}
}
{
"type" : "dog",
"properties" : {
"isGoodBoy" : true
}
}
I'm happy with having to use the custom adapter to write the name of each type, but I need a solution that will automatically serialize the properties for each type rather than having to write them all manually.
This can be done with PolymorphicJsonAdapterFactory and including an extra property in the json to specify the type.
For example:
This JSON
{
"animals": [
{
"type": "dog",
"isGoodBoy": true
},
{
"type": "cat",
"remainingLives": 9
}
]
}
Can be mapped to the following classes
sealed class Animal {
#JsonClass(generateAdapter = true)
data class Dog(val isGoodBoy: Boolean) : Animal()
#JsonClass(generateAdapter = true)
data class Cat(val remainingLives: Int) : Animal()
object Unknown : Animal()
}
With the following Moshi config
Moshi.Builder()
.add(
PolymorphicJsonAdapterFactory.of(Animal::class.java, "type")
.withSubtype(Animal.Dog::class.java, "dog")
.withSubtype(Animal.Cat::class.java, "cat")
.withDefaultValue(Animal.Unknown)
)
I think you need the polymorphic adapter to achieve this which requires the moshi-adapters artifact. This will enable serialization of sealed classes with different properties. More details are in this article here: https://proandroiddev.com/moshi-polymorphic-adapter-is-d25deebbd7c5
I have solved this by creating a Factory, an enclosing class, and an enum that can provide the classes for each item type. However this feels rather clunky and I would love a more straight forward solution.
data class AnimalObject(val type: AnimalType, val properties: Animal)
enum class AnimalType(val derivedClass: Class<out Animal>) {
DOG(Animal.Dog::class.java),
CAT(Animal.Cat::class.java)
}
class AnimalFactory : JsonAdapter.Factory {
override fun create(type: Type, annotations: MutableSet<out Annotation>, moshi: Moshi): JsonAdapter<AnimalObject>? {
if (!Types.getRawType(type).isAssignableFrom(AnimalObject::class.java)) {
return null
}
return object : JsonAdapter<AnimalObject>() {
private val animalTypeAdapter = moshi.adapter<AnimalType>(AnimalType::class.java)
override fun fromJson(reader: JsonReader): AnimalObject? {
TODO()
}
override fun toJson(writer: JsonWriter, value: AnimalObject?) {
writer.beginObject()
writer.name("type")
animalTypeAdapter.toJson(writer, value!!.type)
writer.name("properties")
moshi.adapter<Animal>(value.type.derivedClass).toJson(writer, value.properties)
writer.endObject()
}
}
}
}
Answer is taken from: github.com/square/moshi/issues/813
You should be able to create your own JsonAdapter.Factory and provide custom adapter whenever an Animal need to be serialized/deserialized:
sealed class Animal {
#JsonClass(generateAdapter = true)
data class Dog(val isGoodBoy: Boolean) : Animal()
#JsonClass(generateAdapter = true)
data class Cat(val remainingLives: Int) : Animal()
}
object AnimalAdapterFactory : JsonAdapter.Factory {
override fun create(type: Type, annotations: MutableSet<out Annotation>, moshi: Moshi): JsonAdapter<*>? =
when (type) {
Animal::class.java -> AnimalAdapter(moshi)
else -> null
}
private class AnimalAdapter(moshi: Moshi) : JsonAdapter<Animal>() {
private val mapAdapter: JsonAdapter<MutableMap<String, Any?>> =
moshi.adapter(Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java))
private val dogAdapter = moshi.adapter(Animal.Dog::class.java)
private val catAdapter = moshi.adapter(Animal.Cat::class.java)
override fun fromJson(reader: JsonReader): Animal? {
val mapValues = mapAdapter.fromJson(reader)
val type = mapValues?.get("type") ?: throw Util.missingProperty("type", "type", reader)
val properties = mapValues["properties"] ?: throw Util.missingProperty("properties", "properties", reader)
return when (type) {
"dog" -> dogAdapter.fromJsonValue(properties)
"cat" -> catAdapter.fromJsonValue(properties)
else -> null
}
}
override fun toJson(writer: JsonWriter, value: Animal?) {
writer.beginObject()
writer.name("type")
when (value) {
is Animal.Dog -> writer.value("dog")
is Animal.Cat -> writer.value("cat")
}
writer.name("properties")
when (value) {
is Animal.Dog -> dogAdapter.toJson(writer, value)
is Animal.Cat -> catAdapter.toJson(writer, value)
}
writer.endObject()
}
}
}
private val moshi = Moshi.Builder()
.add(AnimalAdapterFactory)
.build()
#Test
fun test() {
val dog: Animal = Animal.Dog(true)
val cat: Animal = Animal.Cat(7)
println(moshi.adapter(Animal::class.java).toJson(dog))
println(moshi.adapter(Animal::class.java).toJson(cat))
val shouldBeDog: Animal? = moshi.adapter(Animal::class.java).fromJson(moshi.adapter(Animal::class.java).toJson(dog))
val shouldBeCat: Animal? = moshi.adapter(Animal::class.java).fromJson(moshi.adapter(Animal::class.java).toJson(cat))
println(shouldBeDog)
println(shouldBeCat)
}

Get generics class loader for parsing nested Parcelable generic field

I have a wrapper of Parcelable generic type but Parcel constructing fails to compile because T class can not be determined generically
class MyItem<T : Parcelable> (val model: T) : Parcelable {
constructor(parcel: Parcel) :
this(parcel.readParcelable(T::class.java.classLoader)) {
}
}
Is there any solution to this case?
In order to get the whole picture here is what I ended up with:
The use case is one has a Parcelable generic instance let's call it model which should be completed with common properties of Parcelable wrapper in order to not pollute the model with extrinsic fields. For example Item wrapper.
In the example below the wrapper extra property gives us some type of index :
class Item<T : Parcelable> (val model: T, val index: Int ) : Parcelable {
constructor(parcel: Parcel) :
this(parcel.readParcelable(
Item<T>::model.javaClass.classLoader),
parcel.readInt()
) {}
override fun writeToParcel(parcel: Parcel?, flag: Int) {
parcel?.writeParcelable(model, 0)
parcel?.writeInt(index)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Item<Parcelable>> {
override fun createFromParcel(parcel: Parcel): Item<Parcelable> {
return Item(parcel)
}
override fun newArray(size: Int): Array<Item<Parcelable>?> {
return arrayOfNulls(size)
}
}
}
So in the end we can have something like: Item(Person("John"), 0), Item(Person("Bill"), 1)...
class PersonPagerFragment() : BasePagerFragment<Person>() {
companion object {
fun newInstance(itemList: ArrayList<Item<Person>>)
: PersonPagerFragment {
val args = Bundle()
val fragment = PersonPagerFragment()
args.putParcelableArrayList("items", itemList)
fragment.arguments = args
return fragment
}
}
}
extending class like:
class BasePagerFragment<T : Parcelable> : Fragment(){
protected fun readBundle(bundle: Bundle?) {
bundle.getParcelableArrayList<Item<T>>("items")
}
}
You can use a reified inline function as a factory method to achieve this. I prefer to do this on a companion object. Here's an MCVE:
class MyItem<T> (val model: T) {
companion object {
inline fun <reified T> from(parcel : T) : MyItem<T> {
return MyItem<T>(T::class.java.newInstance())
}
}
}
fun main(args: Array<String>) {
val mi = MyItem.from("hi")
println("mi is a ${mi::class}")
}
If you need to have a Parcel-type constructor, you could change that to be the primary constructor, and figure out the type of the MyItem then.
class Parcel
class MyItem(val parcel: Parcel) {
inline fun <reified T> model() : T {
// You code would be calling
// `parcel.readParcelable(T::class.java.classLoader)`
return T::class.java.newInstance() as T
}
}
fun main(args: Array<String>) {
// You don't ned to know the out type when constructing MyItem.
val mi = MyItem(Parcel())
// But you do need to know it when calling model()
val model : String = mi.model()
println("mi.model is a ${model::class}")
}

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