How to upgrade to AndroidX preferences? - androidx

I'm trying to upgrade to the new Androidx preference library. All tutorials I find involve creating masses of intertwined XML files, activities, and fragments, which makes everything much more complicated that it was before.
What I had before:
class PreferenceItem<T>(val key: String, val default: T){
fun read(context: Context): T = PreferenceManager.getDefaultSharedPreferences(context).all.get(key) as T
}
enum class CoordRep { LATLNG, UTM }
class MyPreferences(context: Context){
val coordSys = PreferenceItem("COORD_SYS", CoordRep.LATLNG)
val showSmoothVideo = PreferenceItem("SHOW_SMOOTH_VIDEO", true)
}
class SettingsActivity : PreferenceActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
preferenceScreen = preferenceManager.createPreferenceScreen(this)
val prefs = MyPreferences(this)
preferenceScreen.addPreference(ListPreference(this).apply {
key = prefs.coordSys.key
value = prefs.coordSys.default.name
entries = CoordRep.values().map { when(it){
CoordRep.LATLNG ->"Lat/Long"; CoordRep.UTM ->"UTM"} }.toTypedArray()
entryValues = CoordRep.values().map { it.name }.toTypedArray()
title = "Coordinate Representation"
summary = "Represent Coordinates as: %s"
})
preferenceScreen.addPreference(CheckBoxPreference(this).apply{
key = prefs.showSmoothVideo.key
setDefaultValue(prefs.showSmoothVideo.default)
title = "Smooth Video"
summary = "Enable smooth video"
})
}
Question is - how can I upgrade this code to AndroidX? If I try, I first find that preferenceManager no longer exists because there is no PreferenceActivity for AndroidX.
And trying to do it with
val screen = PreferenceManager(this).createPreferenceScreen(this)
Gives me an error about PreferenceManager can only be called from within the same library group prefix
Is there a straightforward way to switch this over to the newer and supposedly superior AndroidX preference API?

Well, figured it out. Looks like you have to use a fragment.
class PreferenceItem<T>(val key: String, val default: T){
fun read(context: Context): T = PreferenceManager.getDefaultSharedPreferences(context).all.get(key) as T
}
enum class CoordRep { LATLNG, UTM }
class MyPreferences(context: Context){
val coordSys = PreferenceItem("COORD_SYS", CoordRep.LATLNG)
val showSmoothVideo = PreferenceItem("SHOW_SMOOTH_VIDEO", true)
}
class SettingsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.settings_activity)
supportFragmentManager
.beginTransaction()
.replace(R.id.settings_container, SettingsFragment())
.commit()
}
}
class SettingsFragment: PreferenceFragmentCompat(){
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
val context = preferenceManager.context
preferenceScreen = preferenceManager.createPreferenceScreen(context)
preferenceScreen.addPreference(ListPreference(context).apply {
key = prefs.coordSys.key
value = prefs.coordSys.default.name
entries = CoordRep.values().map { when(it){
CoordRep.LATLNG ->"Lat/Long"; CoordRep.UTM ->"UTM"} }.toTypedArray()
entryValues = CoordRep.values().map { it.name }.toTypedArray()
title = "Coordinate Representation"
summary = "Represent Coordinates as: %s"
})
preferenceScreen.addPreference(CheckBoxPreference(context).apply{
key = prefs.showSmoothVideo.key
setDefaultValue(prefs.showSmoothVideo.default)
title = "Smooth Video"
summary = "Enable smooth video"
})
It assume the existence of this silly XML file (which I would be more than happy to get rid of but don't want to waste any more time on this)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="#+id/settings_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Not sure why this is an "improvement" over the old way but at least you won't have pycharm complaining about deprecation anymore, and you get access to some new widgets like SeekBarPreference.

Related

Save an edit text from an alert dialog and use it as a string variable (Kotlin)

I'm really new with android studio and kotlin and I'm having troubles doing an alert dialog that saves the edit text on a variable and then uses it in another activity.
To be clear I want to make an activity where there is a button. If you click on it then the Alert Dialog happens and the text should be store in a variable called "name". Code:
class MainActivity2 : AppCompatActivity() {
var name: String? = null
lateinit var textView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
create.setOnClickListener{
val editAlert = AlertDialog.Builder(this).create()
val editView = layoutInflater.inflate(R.layout.alert_dialog_create, null)
editAlert.setView(editView)
editAlert.setButton(AlertDialog.BUTTON_POSITIVE, "Accept") { _, _ ->
val text = editAlert.alert_dialog_create_btn.text
name= text.toString()
Toast.makeText(this, "Created", Toast.LENGTH_SHORT).show()
}
editAlert.show()
}
}
}
Then, I want that name to be use as my sqlite table name. So I have mi SQLite Helper as this:
val DATABASE_NAME = "Test"
val COL_ID =" id"
val COL_NAME = "name"
class AdminSQLiteOpenHelper(var context: Context) : SQLiteOpenHelper(context, DATABASE_NAME, null, 1) {
var table_name = MainActivity2().name
override fun onCreate(db: SQLiteDatabase) {
val createTable = "CREATE TABLE " + table_name +" (" +
COL_ID +" INTEGER PRIMARY KEY AUTOINCREMENT," +
COL_NAME + " VARCHAR(256))"
db?.execSQL(createTable)
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
}
fun insertData(database :Database){
val db = this.writableDatabase
var cv = ContentValues()
cv.put(COL_NAME, database.nombre))
var result = db.insert(table_name, null, cv)
if(result == -1.toLong())
Toast.makeText(context, "Failed", Toast.LENGTH_SHORT).show()
else
Toast.makeText(context, "Success", Toast.LENGTH_SHORT).show()
}
}
And then, on another Activity, I have the edit text where the user can input the data of the table and at top of it should be an edit text with the table name (that should change if the user input a new name).
Code:
class Main2InsertActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2_insert)
val context = this
setContentView(R.layout.activity_main2_insert)
val tituloView = findViewById<TextView>(R.id.titulo)
tituloView.setText(AdminSQLiteOpenHelper(context).table_name)
insertar.setOnClickListener {
if ((name.text.toString().length > 0)
){
var baseDatos = Database(name.text.toString())
var db = AdminSQLiteOpenHelper(context)
db.insertData(baseDatos)
}else{
Toast.makeText(context, "Rellena todos los campos", Toast.LENGTH_SHORT).show()
}
}
}
}
I think the proble is in MainActivity2 because as much as I put a new name with button create and the alert dialog the value of name never changes, but I don't know how to fix it
Each Activity and Fragment (including dialogs) is a completely separate object with no connection to each other. Using the same name for a variable in two different objects is irrelevant, because they're completely unrelated.
You need to learn how to pass data between Activitys and the like, which is one of the trickier parts of getting your head around how Android apps work (seems to account for a lot of the questions on here too!). It's a pretty big and fundamental subject, so you'll need to learn about it yourself. Here's some links!
Passing Events Back to the Dialog's Host
Using Intents to pass data to Activities
Communicating with fragments
Centralising data with ViewModels
Android's been around a while and there are a lot of ways to do this by now - and I know, there's a lot to learn. At least get familiar with passing data in Intents, and the the idea of calling a method on the parent Activity (from the first link)
var table_name = MainActivity2().name
class MainActivity2 : AppCompatActivity() {
var name: String? = null
I am honestly not sure how to reply. Why do you think that name on the 1st line and name on the 3d line are part of the same object? Why are you trying to instantiate on your own an Activity?
What are you trying to achieve with this project? If it is something that you want to use for yourself and you just want to make it work and you do not want to invest time in learning Android - "this is ok".
Otherwise, if you want to make something that looks like an Android application - everything is just so far from what it should be that StackOverflow can't help. You just need to invest more time.
Why are you not using Room for the Database?

Android Data Binding + Mediator Live Data - Handling Lifecycle Events

In my Android app I have a fragment where the user can simultaneously view and edit some object's attributes.
I'm using an MVVM architecture with data binding and a mediator live data that holds the Relation object being edited. Here's how it works:
Fragment inflates and binds the view (the layout xml).
During this process the Fragment has a ViewModel made.
The ViewModel will fetch the Relation object (and its attributes) from the database, and put it in a MediatorLiveData.
Thanks to data binding and binding adapters, the editText fields get set to the object's attributes automatically.
The user can then edit these editText fields and save.
Upon saving, the ViewModel will get the texts from the editTexts and use them to update the Relation object in the local database
Here's the problem: upon rotating the screen, the fragment gets destroyed and recreated. But I have no way to restore the editText contents. The binding will just reset the editText contents (because we didn't actually update the Relation object attributes yet, we only do that when the user presses 'save').
I can't use a Bundle / savedInstanceState because the binding will just overwrite that. Using a MediatorLiveData to hold the edited contents won't work either because the ViewModel gets destroyed upon rotation, so we lose that data.
Portion of the fragment layout. Note the data variable (viewmodel) and the data binding in the relationNameEditText:
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".presentation.relationdetail.RelationDetailFragment">
<data>
<variable
name="relationDetailViewModel"
type="be.pjvandamme.farfiled.presentation.relationdetail.RelationDetailViewModel" />
</data>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="#dimen/relationDetailLayoutMargin">
<TextView
android:id="#+id/nameLabelTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginBottom="8dp"
android:text="#string/nameLabel"
app:layout_constraintBottom_toTopOf="#+id/relationNameEditText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="#+id/relationNameEditText"
android:layout_width="#dimen/relationNameEditWidth"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:ems="10"
android:inputType="textPersonName"
app:layout_constraintBottom_toTopOf="#+id/editTextChips"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/nameLabelTextView"
app:relationName="#{relationDetailViewModel.relation}" />
The Fragment itself:
class RelationDetailFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding: FragmentRelationDetailBinding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_relation_detail,
container,
false
)
val application = requireNotNull(this.activity).application
val arguments = RelationDetailFragmentArgs.fromBundle(arguments!!)
val relationDataSource = FarFiledDatabase.getInstance(application).relationDao
val relationLifeAreaDataSource = FarFiledDatabase.getInstance(application).relationLifeAreaDao
val viewModelFactory =
RelationDetailViewModelFactory(
arguments.relationId,
relationDataSource,
relationLifeAreaDataSource,
application
)
val relationDetailViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(RelationDetailViewModel::class.java)
binding.relationDetailViewModel = relationDetailViewModel
binding.setLifecycleOwner(this)
// stuff about chips
val textWatcher = object: TextWatcher{ /* */ }
binding.saveButton.isEnabled = false
binding.relationNameEditText.addTextChangedListener(textWatcher)
binding.relationSynopsisEditText.addTextChangedListener(textWatcher)
binding.lifeAreaNowEditText.addTextChangedListener(textWatcher)
// etc.
relationDetailViewModel.enableSaveButton.observe(this, Observer{ /* */})
relationDetailViewModel.showNameEmptySnackbar.observe(this, Observer{ /* */})
relationDetailViewModel.navigateToRelationsList.observe(this, Observer{ /* */})
return binding.root
}
}
The viewmodel:
class RelationDetailViewModel (
private val relationKey: Long?,
val relationDatabase: RelationDao,
val relationLifeAreaDatabase: RelationLifeAreaDao,
application: Application
): AndroidViewModel(application) {
private var viewModelJob = Job()
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
private val relation = MediatorLiveData<Relation?>()
fun getRelation() = relation
private val relationLifeAreas = MediatorLiveData<List<RelationLifeArea?>>()
fun getRelationLifeAreas() = relationLifeAreas
// other LiveData's with backing properties, to trigger UI events
init {
initializeRelation()
}
private fun initializeRelation(){
if(relationKey == null || relationKey == -1L) {
initializeNewRelation()
getAdorableAvatarFacialFeatures()
}
else {
retrieveAvatarUrl()
relation.addSource(
relationDatabase.getRelationWithId(relationKey),
relation::setValue)
relationLifeAreas.addSource(
relationLifeAreaDatabase.getAllRelationLifeAreasForRelation(relationKey),
relationLifeAreas::setValue)
}
}
private fun initializeNewRelation(){
uiScope.launch{
var relationId = insert(Relation(0L,"","",null,false))
initializeLifeAreasForRelation(relationId)
relation.addSource(
relationDatabase.getRelationWithId(
relationId!!),
relation::setValue)
relationLifeAreas.addSource(
relationLifeAreaDatabase.getAllRelationLifeAreasForRelation(
relationId!!),
relationLifeAreas::setValue)
}
}
private fun initializeLifeAreasForRelation(relationId: Long?){
if(relationId != null){
enumValues<LifeArea>().forEach {
uiScope.launch{
var relationLifeArea = RelationLifeArea(0L,relationId,it,"")
insert(relationLifeArea)
}
}
}
}
private fun retrieveAvatarUrl(){
uiScope.launch{
var rel = get(relationKey!!)
var avatarUrl = rel?.avatarUrl
if (avatarUrl.isNullOrEmpty()){
getAdorableAvatarFacialFeatures()
_enableSaveButton.value = true
}
else
_adorableAvatarString.value = rel?.avatarUrl
}
}
private fun getAdorableAvatarFacialFeatures(){
uiScope.launch{
var getFeaturesDeferred = AdorableAvatarApi.retrofitService.getFacialFeatures()
try{
var result = getFeaturesDeferred.await()
_adorableAvatarString.value = "https://api.adorable.io/avatars/face/" +
result.features.eyes.shuffled().take(1)[0] + "/" +
result.features.nose.shuffled().take(1)[0] + "/" +
result.features.mouth.shuffled().take(1)[0] + "/" +
result.features.COLOR_PALETTE.shuffled().take(1)[0]
relation.value?.avatarUrl = _adorableAvatarString.value
} catch(t:Throwable){
// ToDo: what if this fails?? -> Try again later!!
_adorableAvatarString.value = "Failure: " + t.message
}
}
}
fun onEditRelation(
relationNameText: String,
relationSynopsisText: String,
lifeAreaNowText: String,
// etc.
){
_enableSaveButton.value = !compareRelationAttributes(
relationNameText,
relationSynopsisText,
lifeAreaNow.Text,
// etc
)
}
private fun compareRelationAttributes(
relationNameText: String,
relationSynopsisText: String,
lifeAreaNowText: String,
// etc.
): Boolean {
// checks if any of the attributes of the Relation object were changed
// i.e. at least 1 of the editText fields has a text content that does
// does not equal the corresponding attribute of the Relation object
}
fun onSave(
name: String,
synopsis: String,
nowLA: String,
// etc.
){
if(!name.isNullOrEmpty()) {
uiScope.launch {
// update the DB
}
// TODO: this one should go away, need some sort of up button instead
_navigateToRelationsList.value = true
}
else
_showNameEmptySnackbar.value = true
}
// database suspend funs omitted
// ui event handling functions
override fun onCleared(){ /* cancel the viewModelJob */ }
}
The binding adapters:
#BindingAdapter("relationName")
fun TextView.setRelationName(item: Relation?){
item?.let{
text = item.name
}
}
#BindingAdapter("relationSynopsis")
fun TextView.setRelationSynopsis(item: Relation?){
item?.let{
text = item.synopsis
}
}
#BindingAdapter("relationLifeAreaNow")
fun TextView.setLifeAreaNowText(item: List<RelationLifeArea?>?){
item?.let{
item.forEach{
if(it?.lifeArea == LifeArea.EPHEMERA){
text = it.content
}
}
}
}
<!-- etc. -->
So my question is: how do I handle this?
I'm thinking the only solution would be to 1) hold a Relation object with the EDITED attributes, updated whenever the user edits the editText, 2) store this in the database.
But I don't think this would be architecturally sound. Nor am I sure if it would work.
In this case, you can try Two Way Data Binding

How to deserialize number value in Firestore field and read this inside de while/loop using kotlinx.coroutines

In the firestore I created a field named PararUm, type number (it does not have Int, when I enter it manually) and I put value 1.
The problem is that the return has been PararUm(PararUm=1) and not just 1.
(99-below)
When I resolved this, I would have solved the first part of the project.
Regarding the second, I want to use kotlinx.coroutines to work within a while/loop (which queries the value of the PararUm field) in a synchronous, non-asynchronous way (as firebase requires)
Can I do something like???(999-below):
I threw this topic down, but I was not happy1.
99-below:
model
#IgnoreExtraProperties
data class PararUm(
var PararUm: Int? = 0
)
Activity
var db = FirebaseFirestore.getInstance()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var pararumRef =
db.collection("smartmodel").document("xxxxxxxxxxxx")
pararumRef.get().addOnSuccessListener { documentSnapshot ->
var PararUm = documentSnapshot.toObject(PararUm::class.java)
Log.i(ContentValues.TAG, "1999 1999 1999" + PararUm)
}
}
999-below:
while (!FCMotorUmA.value) {
var snapshot = pararumRef.get().await()
    
var pararum = snapshot.toObject(PararUM::class.java)
if (pararum.equals(0)) {
// Do something 1
} else if (pararum.equals(1)) {
// Do something 2
}
}

Bound properties not working

I'm using TornadoFX 1.7.5 and I can't seem to get bound properties to work. I have the below ItemViewModels
class DynamicMenuViewModel : ItemViewModel<DynamicMenu>(DynamicMenu()) {
val title = bind { item?.title?.toProperty() }
val isBold = bind { item?.isBold?.toProperty() }
val routes = bind { item?.routes?.toProperty() }
}
data class DynamicMenu(var title: String = "", var isBold: Boolean = false, var routes: MutableList<MenuRouteViewModel> = mutableListOf())
class MenuRouteViewModel : ItemViewModel<MenuRoute>(MenuRoute()) {
val url = bind { item?.url?.toProperty() }
val title = bind { item?.title?.toProperty() }
val isBold = bind { item?.isBold?.toProperty() }
val showNew = bind { item?.showNew?.toProperty() }
}
data class MenuRoute(var url: String = "", var title: String = "", var showNew: Boolean = false, var isBold: Boolean = false)
Which are bound like this:
//routesController.dynamicMenu is an instance of DynamicMenuViewModel()
textfield(property = routesController.dynamicMenu.title) {
prefWidth = formWidth * .5
gridpaneConstraints {
columnRowIndex(0, 1)
marginLeft = 10.0
columnSpan = 2
marginBottom = 20.0
}
}
checkbox(property = routesController.dynamicMenu.isBold){
gridpaneConstraints {
columnRowIndex(2, 1)
marginLeft = 15.0
marginBottom = 20.0
}
}
Then the following functions commit the models and prints them to the screen when I click a button:
fun onClick(){
commitModel()
println(dynamicMenu.item.toString())
dynamicMenu.item.routes.forEach {
println(it.item.toString())
}
}
fun commitModel(){
dynamicMenu.item.routes.forEach {
it.commit()
}
dynamicMenu.commit()
}
The problem is that when I run the program and edit the textfields and checkboxes then click the button that runs onClick(), the backing item doesn't seem to be getting updated. So none of the updated values are printed to the console.
What am I doing wrong here?
The ViewModel can as you probably know only bind bidirectionally against JavaFX Properties. Your domain objects doesn't contain JavaFX properties, so you need to convert them. However, the toProperty() function you are using only operates on a value, and turns it into a Property. This property has no way of knowing about it's field owner, and hence cannot write back into the domain object.
Luckily, you can use the observable function to make your domain object properties writable as well:
val url = bind { item?.observable(MenuRoute::url) }
Since the observable function operates on a specific instance of a MenuRoute object, it now has enough information to write back into that instance when you commit() the model.
If you would consider changing the properties in your domain objects to be observable, you could write:
val url = bind(MenuRoute::url)
You can use the TornadoFX IDEA plugin inspection "Convert all properties to TornadoFX Properties" to automatically rework your properties. This would transform your MenuRoute object into:
class MenuRoute {
val isBoldProperty = SimpleBooleanProperty(false)
var isBold by isBoldProperty
val showNewProperty = SimpleBooleanProperty(false)
var showNew by showNewProperty
val urlProperty = SimpleStringProperty("")
var url by urlProperty
val titleProperty = SimpleStringProperty("")
var title by titleProperty
}
(You have to manually remove the data modifier on your class. Also beware that the current version of the plugin has a bug in the conversion function that would leave the old parameters - a new version will be released shortly).
If you don't want to do that for various reasons, I was just able to support that nice syntax even for mutable vars like you have, so from TornadoFX 1.7.6 you can use this syntax in your binding statements even if you don't want to change your data classes:
val url = bind(MenuRoute::url)

How can I get the name of a Kotlin property?

I have the following function to access a property's delegate. It uses Kotlin reflection to get a property's name and Java reflection to get the field.
fun Any.getDelegate<T>(prop: KProperty<T>): Any {
return javaClass.getDeclaredField("${prop.name}\$delegate").let {
it.setAccessible(true)
it.get(this)
}
}
The method is used like this:
val delegate = a.getDelegate(A::b)
However, I would prefer to use it like this:
val delegate = a.b.delegate
The problem with the code above is getting the property name of a.b and getting the instance a from a.b. From what I know about Kotlin, this is probably not possible, however I'd like to see if I can clean up my function at all.
To give a bigger picture of what I'm trying do here's my complete code. I want an observable delegate to which I can add and remove observers using the delegate reference and without creating addition variables.
fun Any.addObservable<T>(prop: KProperty<T>, observer: (T) -> Unit) {
getObservableProperty(prop).observers.add(observer)
}
fun Any.getObservableProperty<T>(prop: KProperty<T>): ObservableProperty<T> {
return getDelegate(prop) as ObservableProperty<T>
}
fun Any.getDelegate<T>(prop: KProperty<T>): Any {
return javaClass.getDeclaredField("${prop.name}\$delegate").let {
it.setAccessible(true)
it.get(this)
}
}
class ObservableProperty<T>(
initialValue: T,
initialObservers: Array<(T) -> Unit> = emptyArray()) : ReadWriteProperty<Any?, T> {
private var value = initialValue
public val observers: MutableSet<(T) -> Unit> = initialObservers.toHashSet()
public override fun get(thisRef: Any?, desc: PropertyMetadata): T {
return value
}
public override fun set(thisRef: Any?, desc: PropertyMetadata, value: T) {
this.value = value
observers.forEach { it(value) }
}
}
class A() {
var b by ObservableProperty(0)
}
fun main(args: Array<String>) {
val a = A()
a.addObservable(A::b) {
println("b is now $it")
}
a.b = 1
a.b = 2
a.b = 3
}
Edit:
I just realized that the function also isn't strict because the property delegate field name is referenced by KProperty name, which doesn't require a strong reference to the enclosing class. Here's an example to demonstrate the problem:
class A() {
var foo by ObservableProperty(0)
}
class B() {
var foo by ObservableProperty(0)
}
fun main(args: Array<String>) {
val a = A()
a.addObservable(B::foo) {
println("b is now $it")
}
a.foo = 1
a.foo = 2
a.foo = 3
}
This compiles and runs without error because A::foo and B::foo both result in a field string of "foo$delegate.
Right now reflection is all we can do to get to the delegate object. We are designing a language feature to have direct access to delegate instance, but it's long way to go.
This is how you get the name of a Kotlin Property (although only with an instance of the class). This part will be useful to anyone arriving at this question purely based off its title.
class Stuff(val thing: String)
val stuff = Stuff("cool stuff")
val thingFieldName = "${stuff.thing}\$delegate"
// value of thingFieldName is now "thing"
In terms of getting the delegate itself easier, they say you can now do this:
class Foo {
var bar: String by ReactiveProperty<String>()
}
val foo = Foo()
val bar = foo.bar
val barDelegate = ... // foo.bar$delegate
See ticket.

Resources