Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use ViewTreeLifecycleOwner to reliably get the lifecycle owner for databinding. #186

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -379,13 +379,6 @@ If you attempt to retrieve an adapter from a view right after binding it you may
This is because databinding waits for the next draw pass to run to batch up changes. You can force
it to run immediately by calling `binding.executePendingBindings()`.

### LiveData not working

Live data support has been added in `2.3.0-beta3` and `3.0.0-beta3` (androidx). For most cases it
should 'just work'. However, it uses a bit of reflection under the hood and you'll have to call
`adapter.setLifecycleOwner(owner)` if your containing view does not use databinding. This will be
fixed whenever [this issue](https://issuetracker.google.com/issues/112929938) gets resolved.

## License

Copyright 2015 Evan Tatarka
Expand Down
19 changes: 12 additions & 7 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
dataBinding {
enabled = true
}
Expand All @@ -42,15 +45,17 @@ dependencies {
implementation project(':bindingcollectionadapter-viewpager2')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"

implementation 'androidx.activity:activity:1.2.0-alpha03'
implementation 'androidx.fragment:fragment:1.3.0-alpha03'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.2.0-alpha01'
implementation 'com.google.android.material:material:1.2.0-alpha05'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'androidx.vectordrawable:vectordrawable-animated:1.0.0'
implementation 'androidx.lifecycle:lifecycle-runtime:2.0.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0'
implementation 'androidx.paging:paging-runtime:2.0.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.vectordrawable:vectordrawable-animated:1.1.0'
implementation 'androidx.lifecycle:lifecycle-runtime:2.2.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
implementation 'androidx.paging:paging-runtime:2.1.2'

kapt "com.android.databinding:compiler:$agp_version"
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class FragmentDiffRecyclerView : Fragment() {
savedInstanceState: Bundle?
): View? {
return DiffRecyclerViewBinding.inflate(inflater, container, false).also {
it.setLifecycleOwner(this)
it.lifecycleOwner = this
it.viewModel = viewModel
it.listeners = viewModel
it.executePendingBindings()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class FragmentListView : Fragment() {
savedInstanceState: Bundle?
): View? {
return ListViewBinding.inflate(inflater, container, false).also {
it.setLifecycleOwner(this)
it.lifecycleOwner = this
it.viewModel = viewModel
it.listeners = viewModel
it.executePendingBindings()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class FragmentPagedRecyclerView : Fragment() {
savedInstanceState: Bundle?
): View? {
return PagedRecyclerViewBinding.inflate(inflater, container, false).also {
it.setLifecycleOwner(this)
it.lifecycleOwner = this
it.viewModel = viewModel
it.listeners = viewModel
it.executePendingBindings()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class FragmentRecyclerView : Fragment() {
savedInstanceState: Bundle?
): View? {
return RecyclerViewBinding.inflate(inflater, container, false).also {
it.setLifecycleOwner(this)
it.lifecycleOwner = this
it.viewModel = viewModel
it.listeners = viewModel
it.executePendingBindings()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class FragmentSpinnerView : Fragment() {
savedInstanceState: Bundle?
): View? {
return SpinnerViewBinding.inflate(inflater, container, false).also {
it.setLifecycleOwner(this)
it.lifecycleOwner = this
it.viewModel = viewModel
it.listeners = viewModel
it.executePendingBindings()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class FragmentViewPager2View : Fragment() {
savedInstanceState: Bundle?
): View? {
return Viewpager2ViewBinding.inflate(inflater, container, false).also {
it.setLifecycleOwner(this)
it.lifecycleOwner = this
it.viewModel = viewModel
it.listeners = PagerListeners(viewModel)
it.executePendingBindings()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class FragmentViewPagerView : Fragment() {
savedInstanceState: Bundle?
): View? {
return ViewpagerViewBinding.inflate(inflater, container, false).also {
it.setLifecycleOwner(this)
it.lifecycleOwner = this
it.viewModel = viewModel
it.listeners = PagerListeners(it, viewModel)
it.executePendingBindings()
Expand Down
6 changes: 3 additions & 3 deletions bindingcollectionadapter-paging/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ dependencies {
testImplementation 'org.assertj:assertj-core:3.6.2'
testImplementation 'org.mockito:mockito-core:2.19.0'

androidTestImplementation 'androidx.test:runner:1.1.0'
androidTestImplementation 'androidx.test:rules:1.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'com.squareup.assertj:assertj-android:1.1.0'
androidTestImplementation 'androidx.core:core:1.0.0'
}
Expand Down
6 changes: 3 additions & 3 deletions bindingcollectionadapter-recyclerview/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ dependencies {
testImplementation 'org.assertj:assertj-core:3.6.2'
testImplementation 'org.mockito:mockito-core:2.19.0'

androidTestImplementation 'androidx.test:runner:1.1.0'
androidTestImplementation 'androidx.test:rules:1.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'com.squareup.assertj:assertj-android:1.1.0'
androidTestImplementation 'androidx.core:core:1.0.0'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import androidx.databinding.ViewDataBinding;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewTreeLifecycleOwner;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;

Expand Down Expand Up @@ -62,18 +63,11 @@ public void setItemBinding(@NonNull ItemBinding<? super T> itemBinding) {
* Sets the lifecycle owner of this adapter to work with {@link androidx.lifecycle.LiveData}.
* This is normally not necessary, but due to an androidx limitation, you need to set this if
* the containing view is <em>not</em> using databinding.
*
* @deprecated This is no longer needed. Calling it is a no-op.
*/
@Deprecated
public void setLifecycleOwner(@Nullable LifecycleOwner lifecycleOwner) {
this.lifecycleOwner = lifecycleOwner;
if (recyclerView != null) {
for (int i = 0; i < recyclerView.getChildCount(); i++) {
View child = recyclerView.getChildAt(i);
ViewDataBinding binding = DataBindingUtil.getBinding(child);
if (binding != null) {
binding.setLifecycleOwner(lifecycleOwner);
}
}
}
}

@NonNull
Expand Down Expand Up @@ -266,7 +260,7 @@ public long getItemId(int position) {

private void tryGetLifecycleOwner() {
if (lifecycleOwner == null || lifecycleOwner.getLifecycle().getCurrentState() == Lifecycle.State.DESTROYED) {
lifecycleOwner = Utils.findLifecycleOwner(recyclerView);
lifecycleOwner = ViewTreeLifecycleOwner.get(recyclerView);
}
}

Expand Down
6 changes: 3 additions & 3 deletions bindingcollectionadapter-viewpager2/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ dependencies {
testImplementation 'org.assertj:assertj-core:3.6.2'
testImplementation 'org.mockito:mockito-core:2.19.0'

androidTestImplementation 'androidx.test:runner:1.1.0'
androidTestImplementation 'androidx.test:rules:1.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'com.squareup.assertj:assertj-android:1.1.0'
androidTestImplementation 'androidx.core:core:1.0.0'
}
Expand Down
7 changes: 4 additions & 3 deletions bindingcollectionadapter/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,16 @@ android {

dependencies {
implementation 'androidx.core:core:1.0.0'
implementation "androidx.lifecycle:lifecycle-runtime:2.3.0-alpha01"
implementation 'androidx.legacy:legacy-support-core-ui:1.0.0'

testImplementation 'junit:junit:4.12'
testImplementation 'org.assertj:assertj-core:3.6.2'
testImplementation 'org.mockito:mockito-core:2.19.0'

androidTestImplementation 'androidx.test:runner:1.1.0'
androidTestImplementation 'androidx.test:rules:1.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'com.squareup.assertj:assertj-android:1.1.0'
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import androidx.databinding.ViewDataBinding;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewTreeLifecycleOwner;

/**
* A {@link BaseAdapter} that binds items to layouts using the given {@link ItemBinding} If you give
Expand Down Expand Up @@ -61,10 +62,11 @@ public void setItemBinding(@NonNull ItemBinding<? super T> itemBinding) {
* Sets the lifecycle owner of this adapter to work with {@link androidx.lifecycle.LiveData}.
* This is normally not necessary, but due to an androidx limitation, you need to set this if
* the containing view is <em>not</em> using databinding.
*
* @deprecated No longer needed. Calling is a no-op.
*/
@Deprecated
public void setLifecycleOwner(@Nullable LifecycleOwner lifecycleOwner) {
this.lifecycleOwner = lifecycleOwner;
notifyDataSetChanged();
}

@NonNull
Expand Down Expand Up @@ -214,7 +216,7 @@ public final View getDropDownView(int position, @Nullable View convertView, @Non

private void tryGetLifecycleOwner(View view) {
if (lifecycleOwner == null || lifecycleOwner.getLifecycle().getCurrentState() == Lifecycle.State.DESTROYED) {
lifecycleOwner = Utils.findLifecycleOwner(view);
lifecycleOwner = ViewTreeLifecycleOwner.get(view);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import androidx.databinding.ViewDataBinding;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewTreeLifecycleOwner;
import androidx.viewpager.widget.PagerAdapter;

/**
Expand Down Expand Up @@ -53,15 +54,11 @@ public void setItemBinding(@NonNull ItemBinding<? super T> itemBinding) {
* Sets the lifecycle owner of this adapter to work with {@link androidx.lifecycle.LiveData}.
* This is normally not necessary, but due to an androidx limitation, you need to set this if
* the containing view is <em>not</em> using databinding.
*
* @deprecated No longer needs to be called. Is a no-op
*/
@Deprecated
public void setLifecycleOwner(@Nullable LifecycleOwner lifecycleOwner) {
this.lifecycleOwner = lifecycleOwner;
for (View view : views) {
ViewDataBinding binding = DataBindingUtil.getBinding(view);
if (binding != null) {
binding.setLifecycleOwner(lifecycleOwner);
}
}
}

@NonNull
Expand Down Expand Up @@ -105,9 +102,6 @@ public ViewDataBinding onCreateBinding(@NonNull LayoutInflater inflater, @Layout
public void onBindBinding(@NonNull ViewDataBinding binding, int variableId, @LayoutRes int layoutRes, int position, T item) {
if (itemBinding.bind(binding, item)) {
binding.executePendingBindings();
if (lifecycleOwner != null) {
binding.setLifecycleOwner(lifecycleOwner);
}
}
}

Expand Down Expand Up @@ -154,7 +148,7 @@ public Object instantiateItem(@NonNull ViewGroup container, int position) {

private void tryGetLifecycleOwner(View view) {
if (lifecycleOwner == null || lifecycleOwner.getLifecycle().getCurrentState() == Lifecycle.State.DESTROYED) {
lifecycleOwner = Utils.findLifecycleOwner(view);
lifecycleOwner = ViewTreeLifecycleOwner.get(view);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,11 @@

import android.content.Context;
import android.content.res.Resources;

import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.databinding.ViewDataBinding;

import android.os.Looper;
import android.view.View;

import androidx.annotation.LayoutRes;
import androidx.lifecycle.LifecycleOwner;
import androidx.databinding.DataBindingUtil;
import androidx.databinding.ViewDataBinding;

/**
* Helper databinding utilities. May be made public some time in the future if they prove to be
Expand All @@ -33,25 +27,6 @@ static void throwMissingVariable(ViewDataBinding binding, int bindingVariable, @
throw new IllegalStateException("Could not bind variable '" + bindingVariableName + "' in layout '" + layoutName + "'");
}

/**
* Returns the lifecycle owner associated with the given view. Tries to get lifecycle owner first
* from ViewDataBinding, else from View Context if view is not data-bound.
*/
@Nullable
@MainThread
static LifecycleOwner findLifecycleOwner(View view) {
ViewDataBinding binding = DataBindingUtil.findBinding(view);
LifecycleOwner lifecycleOwner = null;
if (binding != null) {
lifecycleOwner = binding.getLifecycleOwner();
}
Context ctx = view.getContext();
if (lifecycleOwner == null && ctx instanceof LifecycleOwner) {
lifecycleOwner = (LifecycleOwner) ctx;
}
return lifecycleOwner;
}

/**
* Ensures the call was made on the main thread. This is enforced for all ObservableList change
* operations.
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@
# org.gradle.parallel=true

group=me.tatarka.bindingcollectionadapter2
version=4.0.0
version=4.1.0-SNAPSHOT
android.useAndroidX=true
android.enableJetifier=true