// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.ui;
import android.view.View;
import androidx.annotation.Nullable;
import org.chromium.base.Callback;
import org.chromium.base.ThreadUtils;
/**
* A provider that encapsulates a {@link View} that is in the view hierarchy to be inflated by
* an {@link AsyncViewStub}.
* @param <T> type of the {@link View} that this provider encapsulates.
*/
public class AsyncViewProvider<T extends View> implements Callback<View>, ViewProvider<T> {
private int mResId;
// Exactly one of mView and mViewStub is non-null at any point.
private T mView;
private AsyncViewStub mViewStub;
private boolean mDestroyed;
private AsyncViewProvider(AsyncViewStub viewStub, int resId) {
assert viewStub != null;
mResId = resId;
mViewStub = viewStub;
}
@SuppressWarnings("unchecked")
private AsyncViewProvider(View view) {
assert view != null;
mView = (T) view;
}
/**
* Returns a provider for a view in the view hierarchy that is to be inflated by {@param
* viewStub}.
* @param viewStub the {@link AsyncViewStub} that will inflate the view hierarchy containing the
* {@link View}.
* @param resId The resource id of the view that this provider should provide/encapsulate.
* @return an {@link AsyncViewProvider} that encapsulates a view with id {@param resId}.
*/
public static <E extends View> AsyncViewProvider<E> of(AsyncViewStub viewStub, int resId) {
ThreadUtils.assertOnUiThread();
if (viewStub.getInflatedView() != null) {
return new AsyncViewProvider<>(viewStub.getInflatedView().findViewById(resId));
}
AsyncViewProvider<E> provider = new AsyncViewProvider<>(viewStub, resId);
viewStub.addOnInflateListener(provider);
return provider;
}
/**
* Get a provider for a view with id {@param viewResId} that is (or going to be) in the view
* hierarchy inflated by the AsyncViewStub with id {@param viewStubResId}.
* @param root the {@link View} to use as the context for finding the View/ViewStub that the
* provider encapsulates.
* @param viewStubResId the resource id of the AsyncViewStub that inflates the view hierarchy
* where the encapsulated View lives.
* @param viewResId the resource id of the view that the provider should provide/encapsulate.
* @return an {@link AsyncViewProvider} that encapsulates a view with id {@param viewResId}.
*/
public static <E extends View> AsyncViewProvider<E> of(
View root, int viewStubResId, int viewResId) {
ThreadUtils.assertOnUiThread();
View viewStub = root.findViewById(viewStubResId);
if (viewStub != null && viewStub instanceof AsyncViewStub) {
// view stub not yet inflated
return of((AsyncViewStub) viewStub, viewResId);
}
// view stub already inflated, return pre-loaded provider
return new AsyncViewProvider<>(root.findViewById(viewResId));
}
@Override
public void onResult(View view) {
mView = view.findViewById(mResId);
mViewStub = null;
}
/**
* @return the {@link View} encapsulated by this provider or null (if the view has not been
* inflated yet).
*/
@Nullable
public T get() {
return mView;
}
/**
* @param resId resource id of the {@link View} that the returned provider would
* encapsulate.
* @param <E> type of the {@link View} that the returned provider would encapsulate
* @return a provider for a {@link View} with resource id {@param resId} that is in the view
* hierarchy of the {@link View} encapsulated by this provider.
*/
public <E extends View> AsyncViewProvider<E> getChildProvider(int resId) {
if (mView != null) {
return new AsyncViewProvider<>(mView.findViewById(resId));
}
return of(mViewStub, resId);
}
@Override
public void inflate() {
mViewStub.inflate();
}
@Override
public void whenLoaded(Callback<T> callback) {
ThreadUtils.assertOnUiThread();
if (mDestroyed) return;
if (mView != null) {
// fire right now if view already inflated.
callback.onResult(mView);
} else {
mViewStub.addOnInflateListener(
(View view) -> {
if (mDestroyed) return;
// listeners are called in order so mView should be set correctly at this
// point.
callback.onResult(mView);
});
}
}
/**
* Destroy the provider making sure that all queued up after inflate callbacks are no longer
* called.
*/
public void destroy() {
destroy(null);
}
/**
* Same as {@link #destroy()} but takes a callback that is ensured to be run (either immediately
* if the view is already inflated or after inflation of the {@link AsyncViewStub})).
*/
public void destroy(Callback<T> destroyCallback) {
mDestroyed = true;
if (mView != null) {
destroyCallback.onResult(mView);
mView = null;
}
if (mViewStub != null) {
mViewStub.addOnInflateListener(
(View view) -> {
destroyCallback.onResult(mView);
});
mViewStub = null;
}
}
}