https://kotlinlang.org logo
#android
Title
# android
u

ursus

07/24/2018, 10:59 PM
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

gildor

07/24/2018, 11:24 PM
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

ursus

07/24/2018, 11:26 PM
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

gildor

07/24/2018, 11:26 PM
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

ursus

07/24/2018, 11:27 PM
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

gildor

07/24/2018, 11:32 PM
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

ursus

07/24/2018, 11:33 PM
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

gildor

07/24/2018, 11:34 PM
No, as I said, you shouldn't touch view directly any more, all you logic will be in you component
u

ursus

07/24/2018, 11:34 PM
you mean like component.setText() ? proxying all of the methods needed?
g

gildor

07/24/2018, 11:34 PM
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

ursus

07/24/2018, 11:36 PM
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

gildor

07/24/2018, 11:40 PM
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

ursus

07/24/2018, 11:42 PM
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

gildor

07/24/2018, 11:42 PM
Not exactly, I will show an example later, when will be in the office
u

ursus

07/24/2018, 11:43 PM
what could be different? you just hide the views inside some object where they are vals
g

gildor

07/25/2018, 1:23 AM
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

ursus

07/25/2018, 1:44 AM
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

gildor

07/25/2018, 1:46 AM
needed some other fragment callbacks, permissions, activity
Of course, activity/fragment have to delegate such component specific things
u

ursus

07/25/2018, 1:48 AM
uhm noob question, if onViewCreated returns, wont SearchBinding instance go out of scope and gced?
or do you rely on the view reference?
g

gildor

07/25/2018, 1:49 AM
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

ursus

07/25/2018, 1:51 AM
right, intuitively I think that too, but if nobody references you, you can get gced, regardless of your references no?
g

gildor

07/25/2018, 1:51 AM
actually SearchBinding also has reference to lifecycle
u

ursus

07/25/2018, 1:52 AM
right but view doesnt reference the binding
g

gildor

07/25/2018, 1:52 AM
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

ursus

07/25/2018, 1:55 AM
which view
textview1? etc?
g

gildor

07/25/2018, 1:56 AM
yes, if you save textview1 to field of fragment you must set this field to null in onDestroyView
u

ursus

07/25/2018, 1:57 AM
yes, pseudocode
g

gildor

07/25/2018, 1:58 AM
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

ursus

07/25/2018, 1:59 AM
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

gildor

07/25/2018, 2:00 AM
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

ursus

07/25/2018, 2:01 AM
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

gildor

07/25/2018, 2:01 AM
again, depends on adapter implementation of course
u

ursus

07/25/2018, 2:01 AM
how do you detach adapter?
recyclerview.setadapter null ?
g

gildor

07/25/2018, 2:02 AM
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

ursus

07/25/2018, 2:03 AM
yea
hardly anything is outside of view scope in fragments for me anyways
g

gildor

07/25/2018, 2:04 AM
Yes, usually easier to keep data and load it on ViewModel and then update adapter
u

ursus

07/25/2018, 2:04 AM
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

gildor

07/25/2018, 2:05 AM
I’m definitely sure
u

ursus

07/25/2018, 2:06 AM
that means if some silly data class has reference to singleton object, it never gets gced?
g

gildor

07/25/2018, 2:07 AM
No, it will be gced
u

ursus

07/25/2018, 2:08 AM
then how, you said direction of refs doesnt matter,
g

gildor

07/25/2018, 2:08 AM
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

ursus

07/25/2018, 2:12 AM
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

gildor

07/25/2018, 2:21 AM
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

ursus

07/25/2018, 2:22 AM
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

gildor

07/25/2018, 2:25 AM
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

ursus

07/25/2018, 2:26 AM
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

gildor

07/25/2018, 2:38 AM
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

ursus

07/25/2018, 2:38 AM
right, but then they get that FooKt name, and it doesnt allow me to @JvmName
if I also declare the class later
g

gildor

07/25/2018, 2:40 AM
If you want to keep names for those top level functions, use companion object + @JvmStatic
u

ursus

07/25/2018, 2:41 AM
bummer, thats a wasteful object created, I dont need state
thnx
g

gildor

07/25/2018, 2:47 AM
Do you really so worry about one object creation?
u

ursus

07/25/2018, 2:48 AM
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

gildor

07/25/2018, 2:48 AM
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

ursus

07/25/2018, 2:50 AM
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