in fragments, what kind of properties do you use f...
# android
u
in fragments, what kind of properties do you use for view scoped things? Like adapters, and actual views .. private val adapter: Adapter? = null and then !! everywhere?
g
You cannot use val in this case, only var
We use nullable var and
?
rather than
!!
Also good approach to create scoped component with view lifecycle
u
lateinit var is fine, but, in fragments, unlike activities, stuff can live between onCreateView / onDestroyView, like views... and whats the usual pattern then, !! everywhere?
g
Also adapter is not necessary should be recreated on view destroy, you can just detach it
But approach with separate view scope is the best
u
well, sure, if its outside of onDestroyView then ?., i.e. its null regardless so it should just no-op, but what about accessing the propery in its legal lifecycle, throwing would be more appropriate, but sort of a pain because I dont expect it to be null there anyways
what do you mean that lifecycle scoped thing? but then youd have to refer to the containing objects always via component.foo right? what about the component property it self
g
I mean create some class where you pass root view to the constructor and this class binds all the view references on creation, so no nullable fields, same for any other dependencies with view lifecycle No need to delegate all the calls, rather move as much logic as you can to this view scoped component, so of course some time you need to call component?. something, but not so often
u
i dont get it, you just move the problem from the all fields, to one field of that property, and add the penalty of indirection
and anytime youd need to touch some views it contains, youd have to component?.someView
g
No, as I said, you shouldn't touch view directly any more, all you logic will be in you component
u
you mean like component.setText() ? proxying all of the methods needed?
g
No, I don't mean that
I mean that component itself does all those things
Not a fragment
It really depends on your architecture, for example in case of MVVM everything works like that by default
Same for MVP actually
u
okay now I see, idk, it doesnt solve it completely and you also get the indirection
what does it have to do with architecture? Its just setting data on dumb views, ui
g
It relates, because some architecture approaches force you to extract interaction with views, so you can easily extract view binding
Approach with component with views also can helpful, because you have to check that component is not nullable only once and use views directly without safe cast or double bang
u
Copy code
class SearchFragment {
	fun override onCreateView() {
		disposables += viewModel.searchStateObservable()
	                .throwingSubscribe { handleSearchState(it) }    }

     private fun handleSearchState(searchState: SearchState) {
        textView1!!.setText
        textView2!!.setText
        textView3!!.setText
    }
}

class SearchFragment {
	private var component: Component? = null
	fun override onCreateView() {
		this.component = Component(view)

		disposables += viewModel.searchStateObservable()
	                .throwingSubscribe { component.handleSearchState(it) }    }
	}
}
so youre saying this?
g
Not exactly, I will show an example later, when will be in the office
u
what could be different? you just hide the views inside some object where they are vals
g
Something like that:
Copy code
class SearchFragment : Fragment {
    private val viewModel: SearchViewModel = TODO("Create ViewModel or inject it")

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        SearchBinding(view, viewModel, lifecycle)
    }
}


class SearchBinding(root: View, viewModel: SearchViewModel, lifecycle: Lifecycle) {
    private val textView1: TextView = root.findViewById(R.id.view1)
    private val textView2: TextView = root.findViewById(R.id.view2)
    private val textView3: TextView = root.findViewById(R.id.view3)

    init {
        val disposable = viewModel.searchStateObservable().throwingSubscribe(::handleSearchState)
        lifecycle.onDestroy(disposable::dispose)
    }

    private fun handleSearchState(searchState: SearchState) {
        textView1.text = searchState.text1
        textView2.text = searchState.text2
        textView3.text = searchState.text3
    }
}
Also you can improve it, if SearchBinding will be disposable itself but in this case it should probably extend some base class, again up to you less code or compisition instead of inheritance
Changed example to use lifecycle to manage subscription. Also can be improved if you add extension function: `fun Disposable.bindTo(lifecycle: Lifecycle)`: You init method will look like:
Copy code
init {
        viewModel
             .searchStateObservable()
             .throwingSubscribe(::handleSearchState)
             .bindTo(lifecycle)
    }
u
interesting
then you can transplant the binding to activity view fragment whatever
but will burn you if you needed some other fragment callbacks, permissions, activity result etc., but yea nice idea
g
needed some other fragment callbacks, permissions, activity
Of course, activity/fragment have to delegate such component specific things
u
uhm noob question, if onViewCreated returns, wont SearchBinding instance go out of scope and gced?
or do you rely on the view reference?
g
some other fragment callbacks
Android Team already tries to extract as much as possible features of fragments, like navigation, so hope in future it will be even more simple. And nothing prevent you from delegate activity/fragment events to binding, or what I mean mentioning Architecture, delegate those calls not to binding or views, but to ViewModel, presenter or any other part that works with business logic
No,, SearchBinding has reference on View so cannot be collected while View is alive
u
right, intuitively I think that too, but if nobody references you, you can get gced, regardless of your references no?
g
actually SearchBinding also has reference to lifecycle
u
right but view doesnt reference the binding
g
but if nobody references you, you can get gced, regardless of your references
No, if an object has hard reference to some other object that is also has reference to GC root, such object cannot be collected upd: Sorry, of course not like that, I thought that we listen some events of view
Doens’t make sense, this is hard reference, doesn’t metter direction of reference
Actually it’s funny, that you cannot just save view to field (like in your original code), you have to set this field to null on Fragment, otherwise you will get memory leak
u
which view
textview1? etc?
g
yes, if you save textview1 to field of fragment you must set this field to null in onDestroyView
u
yes, pseudocode
g
yes, but your original question was about handling nullable fields that related to view lifecycle and you always should be care to caching anything that reference view if you don’t want to leak fragment
u
i dont think so but I dont bother searching, yea youre right, null out the refs in onDestroyView
thats kind of that I was saying about the adapters, etc, view-refering objects, that they need to be nulled out so they dont live after onDestroyView
g
Which is also ugly and error prone
Why adapter reference views in your case?
I mean it also fine and you can move it to SearchBinding and adapter will have the same lifecycle as view
but usually you can just detach adapter and reuse it on view recreation
u
i mean i didnt exactly look if adapter holds some view reference, possibly not, but I just think of it as view scoped thing
g
again, depends on adapter implementation of course
u
how do you detach adapter?
recyclerview.setadapter null ?
g
recycleView.adapter = null
yes
But again, it highly depends on adapter implementation, maybe would be easier just create a new adapter, especially if adapter is dumb and you just set adapter state from ViewModel
u
yea
hardly anything is outside of view scope in fragments for me anyways
g
Yes, usually easier to keep data and load it on ViewModel and then update adapter
u
yup..
r u sure about the references thing? thats kind of news to me, I always though that GC traverses the graphs and cuts of leaves
g
I’m definitely sure
u
that means if some silly data class has reference to singleton object, it never gets gced?
g
No, it will be gced
u
then how, you said direction of refs doesnt matter,
g
sorry, you right, because we do not listen view events in this case everything is fine, we reference only viewModel and lifecycle (because pass lambda to lifecycle)
usually you also listen view events and in this case you have hard reference
u
im confused, you need view reference to findViewByIds
Copy code
The object will not become a candidate for garbage collection until all references to it are discarded. Java objects are assigned by reference so when you had....
g
need view reference to findViewByIds
?
The object will not become a candidate for garbage collection until all references to it are discarded
Correct. Why do you confused? In your example view doesn’t have reference to SearchBinding, but observable has and lifecycle has those references (because of callbacks)
u
oh that is it, okay so I was right in my logic
but yea observable has transitively reference to enclosing class
then you get auto "nulled out" then lifecycle gets disposed, nice
btw is assigning lateinit var twice cool in your opinion?
g
It’s kinda fine if you need this, but you cannot set null to lateinit, so not sure about real usecases for this, at least on Android
u
right, thanks
do you know, do they plan on having sort of a Lifecycle object equivalent to permissions?
one aside: im migrating java to kotlin, I have a class which also had public static method, I want to make it toplevel in kotlin, but static on that class in java, is that possible without companion object?
g
I don’t know about particular plans. But you can implement some helper yourself, depends on API which you want to use. Simplest case just call “onPermissionGranted()” callback on your Presenter/ViewModel/any other abstraction when Activity/Fragment handled intent
You can make them top level or move to companion object, no other ways. Why do you looking for some other solution?
u
right, but then they get that FooKt name, and it doesnt allow me to @JvmName
if I also declare the class later
g
If you want to keep names for those top level functions, use companion object + @JvmStatic
u
bummer, thats a wasteful object created, I dont need state
thnx
g
Do you really so worry about one object creation?
u
no, but its pointless so it shouldnt be there, Ive checked bytecode, companion object egts instantiated even tho it has nothing in it, ie. everytinh is @jvmstatic const
g
You create 1 object on each boxed primitive, this is just one more object on first access
One of the reasons why companion object is there, that you also can write extension for this companion, like:
fun Foo.Companion.bar()
so can use
Foo.bar()
from Kotlin
u
idk how I feel about companion object, "static" stuff should be pure functions only, i.e. toplevel funcs
you already have
object
syntactic sugar for process scoped things, so I kind of dont see the poitn of them being there honestly