kotterknife icon indicating copy to clipboard operation
kotterknife copied to clipboard

Bind view in retained Fragment

Open damianpetla opened this issue 10 years ago • 9 comments

@JakeWharton I was wondering if you tried to handle binding views in retained Fragments?

I will explain the problem to everyone else: When you set your fragment retained calling setRetainInstance(true) view properties once created are kept in memory after fragment layout is re-created e.g. screen orientation changes. It means that after orientation change properties points to the view from the old invalid layout reference. With java ButterKnife you have to call inject() in respective fragment method callback. In case this library injection is done automatically whenever view is used for the first time.

damianpetla avatar Dec 21 '14 15:12 damianpetla

I don't use fragments, but I understand the requirement. ButterKnife has reset(Object) for this purpose.

JakeWharton avatar Dec 22 '14 00:12 JakeWharton

ButterKnife works great but KotterKnife does not have such functionality. So is there a chance that you will try to take care of that issue? If not, I will try myself but I am still learning Kotlin so it may take some time :)

damianpetla avatar Dec 22 '14 10:12 damianpetla

@JakeWharton, could you please merge my pull request that fixes this issue?

MichaelRocks avatar Feb 14 '15 19:02 MichaelRocks

Wouldn't be better to have a single object that manages all the lazy values in the fragment that is updated in onCreateView() in order to invalidate all the cached values? I will write and submit a pull request in the future

clynamen avatar Mar 20 '15 21:03 clynamen

I have the same issue with Fragments. Also happens when setRetainInstance(true) was not set. Let's say a Fragment is detached and then reattached. The same Fragment instance will be reused however it will go through onCreateView() and onViewCreated() again. So the Views are new instances but the properties still point to the old View instances.

private val textView: TextView by bindView(R.id.text)

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) =
        inflater.inflate(R.layout.fragment_main, container, false)

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    // Ooops, won't work when the Fragment is reattached 
    // because an old instance is accessed    
    textView.setText("This is a test")
}

svenjacobs avatar Jul 19 '15 16:07 svenjacobs

So by this logic every use of lazy property in activity or fragment is doomed to fail? Is it safe to do something like this:

val category: String by lazy { getIntent().getStringExtra(EXTRA_CATEGORY) }

pftbest avatar Jul 20 '15 08:07 pftbest

So by this logic every use of lazy property in activity or fragment is doomed to fail?

Well, if it's a value that might change and needs to be recalculated on every use of the Activity/Fragment instance I would say yes, lazy cannot be used then.

svenjacobs avatar Jul 20 '15 09:07 svenjacobs

I'm also having this issue today with my FragmentPagerAdapter. After navigating far enough away from a fragment in a ViewPager, the FragmentPagerAdapter will destroy the view of that Fragment, but the Fragment itself and everything to which it holds a reference will remain in memory. Upon recreation of the Fragment's view, the view bindings are not reset.

If anyone has come up with a good workaround for this I'd appreciate your input :+1:

EDIT: Nevermind, I just merged the PR from @MichaelRocks into my project, and it works perfectly. Thanks!

damien5314 avatar Nov 13 '15 00:11 damien5314

Another option is to have methods like

@Suppress("UNCHECKED_CAST")
fun <V: View> SupportFragment.bindViewNoCache(@IdRes id: Int): ReadOnlyProperty<SupportFragment, V> {
    return object: ReadOnlyProperty<SupportFragment, V> {
        override fun getValue(thisRef: SupportFragment, property: KProperty<*>): V {
            return thisRef.view!!.findViewById(id) as V
        }
    }
}
val myView: TextView by bindViewNoCache(R.id.my_view_id)

I realize this is the same as

val myView get() = view!!.findViewById(R.id.my_view_id) as TextView

but it is slightly shorter and allows consistent access syntax with bindView

rabidaudio avatar Mar 15 '17 23:03 rabidaudio