I cannot use synthetic imports directly when referencing view widgets from controller.
Ideally, I should be able to access the views like loginButton.text = "Hello", but am unable to do so. However, I can access the views via my layout id, ie like loginView.loginButton.text = "Hello". On checking the loginButton, teh IDE takes me to R.java file and shows the compiled ID of the view. Is there something I'm missing?
Create an abstract BaseController that implements the interface LayoutContainer.
Add the required containerView:
override val containerView: View?
get() = view
And clear the view cache when the controller's view is destroyed:
override fun onDestroyView(view: View) {
super.onDestroyView(view)
clearFindViewByIdCache()
}
Hi @inorichi, thanks for the hint but doesn't seem to be working for me. Still need to reference view.viewId. Am I missing something?
Also, I was wondering, what is that clearFindViewByIdCache() function?
Thanks.
You may have to add this at the end of your build.gradle
androidExtensions {
experimental = true
}
The synthetic properties are no magic. Kotlins compiler does not know what a Controller is.
Check https://kotlinlang.org/docs/tutorials/android-plugin.html
@GurpreetSK95 you can access views through the getView() method, which becomes just view on Kotlin.
Then it looks like this:
view?.my_text_view?.text = "text"
You need to use ?. since getView is Nullable.
If you don't like it, you can do the following too:
view?.run {
my_text_view.text = "text"
}
OR use the proposed solution by @inorichi which also supports view caching.
You can watch my talk (self promo time) where I explain how to access views with Kotlin's KAE: https://youtu.be/rsmvQBGJYcM?t=15m43s (link opens the section where I explain it)
Hi! Thanks for all the feedback and help. Reverting to you on the same, the issue is I don't want to do view.someView.something everytime I'm accessing or setting something. (It kinda defeats the purpose?) Also, with @inorichi 's way, doesn't clearing view cache all an overhead of creating the same view everytime a controller is resumed?
The LayoutContainer interface does not "create" views, it just calls findViewById on containerView and save a reference so that the next time you use the same view it doesn't need to call findViewById again.
When onDestroyView is called, you should release any reference to your views and (in most cases) anything that takes an activity context, and that's what we're doing here.
You should keep in mind that @inorichi method won't work for the onCreateView callback, bcs view won't be available in controller until onCreateView returned.
@ar-g You can just store a field.
For example this is my BaseController:
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.bluelinelabs.conductor.RestoreViewOnCreateController
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.*
abstract class BaseController(args: Bundle = Bundle()) : RestoreViewOnCreateController(args),
LayoutContainer {
abstract val layoutRes: Int
private var _containerView: View? = null
override val containerView: View? get() = _containerView
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup,
savedViewState: Bundle?
): View {
val view = inflater.inflate(layoutRes, container, false).also {
_containerView = it
}
onViewCreated()
return view
}
final override fun onDestroyView(view: View) {
super.onDestroyView(view)
onDestroyView()
clearFindViewByIdCache()
_containerView = null
}
open fun onViewCreated() {}
open fun onDestroyView() {}
}
In onCreateView I'm creating the view and holding a reference. This _containerView is the backing property. This gets nulled in onDestroyView.
True, I forgot to mention that. What I did was to add a lifecycle listener to the base controller:
abstract class BaseController(
bundle: Bundle? = null
) : RestoreViewOnCreateController(bundle),
LayoutContainer {
init {
addLifecycleListener(object : LifecycleListener() {
override fun postCreateView(controller: Controller, view: View) {
onViewCreated(view)
}
})
}
override val containerView: View?
get() = view
override fun onDestroyView(view: View) {
super.onDestroyView(view)
clearFindViewByIdCache()
}
open fun onViewCreated(view: View) { }
}
Both options work, so pick whichever you like most.
By the way @GurpreetSK95, if you had your issue solved, can you close this issue?
@ar-g You can just store a field.
For example this is my BaseController:
import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.bluelinelabs.conductor.RestoreViewOnCreateController import kotlinx.android.extensions.LayoutContainer import kotlinx.android.synthetic.* abstract class BaseController(args: Bundle = Bundle()) : RestoreViewOnCreateController(args), LayoutContainer { abstract val layoutRes: Int private var _containerView: View? = null override val containerView: View? get() = _containerView override fun onCreateView( inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle? ): View { val view = inflater.inflate(layoutRes, container, false).also { _containerView = it } onViewCreated() return view } final override fun onDestroyView(view: View) { super.onDestroyView(view) onDestroyView() clearFindViewByIdCache() _containerView = null } open fun onViewCreated() {} open fun onDestroyView() {} }In onCreateView I'm creating the view and holding a reference. This
_containerViewis the backing property. This gets nulled in onDestroyView.
This approach doesn't work. The problem is that clearFindViewByIdCache () calls for BaseController, not for inheritor. It leads to view leaks and moreover you will always get old reference. This happens when you have controller A and controller B. Now you are on A, then you go to B, and then you turn back to B.
The solution is to call clearFindViewByIdCache () in each inheritor controller. It brings boilerplate code but it woks.
Most helpful comment
True, I forgot to mention that. What I did was to add a lifecycle listener to the base controller:
Both options work, so pick whichever you like most.
By the way @GurpreetSK95, if you had your issue solved, can you close this issue?