diff options
author | calderwoodra <calderwoodra@google.com> | 2018-04-11 13:50:14 -0700 |
---|---|---|
committer | Copybara-Service <copybara-piper@google.com> | 2018-04-11 17:18:59 -0700 |
commit | a5a7d014d36fc42a428794b7d62070549cc8ba7b (patch) | |
tree | 5514dabc0a33afbcd21c16f82876ab490071b34b /java | |
parent | d4d9fb5e6dcaa284c955d112f21cc420f0f7f3f2 (diff) |
Add support for support fragments in UiListener.
Bug: 36841782
Test: SupportUiListenerTest
PiperOrigin-RevId: 192502743
Change-Id: Id06ed732528db1ae486def86ecc2f44828635d81
Diffstat (limited to 'java')
4 files changed, 182 insertions, 26 deletions
diff --git a/java/com/android/dialer/common/concurrent/DialerExecutorComponent.java b/java/com/android/dialer/common/concurrent/DialerExecutorComponent.java index 734891430..f4552b203 100644 --- a/java/com/android/dialer/common/concurrent/DialerExecutorComponent.java +++ b/java/com/android/dialer/common/concurrent/DialerExecutorComponent.java @@ -50,6 +50,15 @@ public abstract class DialerExecutorComponent { return UiListener.create(fragmentManager, taskId); } + /** + * Version of {@link #createUiListener(FragmentManager, String)} that accepts support fragment + * manager. + */ + public <OutputT> SupportUiListener<OutputT> createUiListener( + android.support.v4.app.FragmentManager fragmentManager, String taskId) { + return SupportUiListener.create(fragmentManager, taskId); + } + public static DialerExecutorComponent get(Context context) { return ((DialerExecutorComponent.HasComponent) ((HasRootComponent) context.getApplicationContext()).component()) diff --git a/java/com/android/dialer/common/concurrent/SupportUiListener.java b/java/com/android/dialer/common/concurrent/SupportUiListener.java new file mode 100644 index 000000000..5e3958619 --- /dev/null +++ b/java/com/android/dialer/common/concurrent/SupportUiListener.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.dialer.common.concurrent; + +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.MainThread; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.DialerExecutor.FailureListener; +import com.android.dialer.common.concurrent.DialerExecutor.SuccessListener; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; + + +/** + * A headless fragment for use in UI components that interact with ListenableFutures. + * + * <p>Callbacks are only executed if the UI component is still alive. + * + * <p>Example usage: <code><pre> + * public class MyActivity extends AppCompatActivity { + * + * private SupportUiListener<MyOutputType> uiListener; + * + * public void onCreate(Bundle bundle) { + * super.onCreate(bundle); + * + * // Must be called in onCreate! + * uiListener = DialerExecutorComponent.get(context).createUiListener(fragmentManager, taskId); + * } + * + * private void onSuccess(MyOutputType output) { ... } + * private void onFailure(Throwable throwable) { ... } + * + * private void userDidSomething() { + * ListenableFuture<MyOutputType> future = callSomeMethodReturningListenableFuture(input); + * uiListener.listen(this, future, this::onSuccess, this::onFailure); + * } + * } + * </pre></code> + */ +public class SupportUiListener<OutputT> extends Fragment { + + private CallbackWrapper<OutputT> callbackWrapper; + + @MainThread + static <OutputT> SupportUiListener<OutputT> create( + FragmentManager fragmentManager, String taskId) { + @SuppressWarnings("unchecked") + SupportUiListener<OutputT> uiListener = + (SupportUiListener<OutputT>) fragmentManager.findFragmentByTag(taskId); + + if (uiListener == null) { + LogUtil.i("SupportUiListener.create", "creating new SupportUiListener for " + taskId); + uiListener = new SupportUiListener<>(); + // When launching an activity with the screen off, its onSaveInstanceState() is called before + // its fragments are created, which means we can't use commit() and need to use + // commitAllowingStateLoss(). This is not a problem for SupportUiListener which saves no + // state. + fragmentManager.beginTransaction().add(uiListener, taskId).commitAllowingStateLoss(); + } + return uiListener; + } + + /** + * Adds the specified listeners to the provided future. + * + * <p>The listeners are not called if the UI component this {@link SupportUiListener} is declared + * in is dead. + */ + @MainThread + public void listen( + Context context, + @NonNull ListenableFuture<OutputT> future, + @NonNull SuccessListener<OutputT> successListener, + @NonNull FailureListener failureListener) { + callbackWrapper = + new CallbackWrapper<>(Assert.isNotNull(successListener), Assert.isNotNull(failureListener)); + Futures.addCallback( + Assert.isNotNull(future), + callbackWrapper, + DialerExecutorComponent.get(context).uiExecutor()); + } + + private static class CallbackWrapper<OutputT> implements FutureCallback<OutputT> { + private SuccessListener<OutputT> successListener; + private FailureListener failureListener; + + private CallbackWrapper( + SuccessListener<OutputT> successListener, FailureListener failureListener) { + this.successListener = successListener; + this.failureListener = failureListener; + } + + @Override + public void onSuccess(@Nullable OutputT output) { + if (successListener == null) { + LogUtil.i("SupportUiListener.runTask", "task succeeded but UI is dead"); + } else { + successListener.onSuccess(output); + } + } + + @Override + public void onFailure(Throwable throwable) { + LogUtil.e("SupportUiListener.runTask", "task failed", throwable); + if (failureListener == null) { + LogUtil.i("SupportUiListener.runTask", "task failed but UI is dead"); + } else { + failureListener.onFailure(throwable); + } + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + // Note: We use commitAllowingStateLoss when attaching the fragment so it may not be safe to + // read savedInstanceState in all situations. (But it's not anticipated that this fragment + // should need to rely on saved state.) + } + + @Override + public void onDetach() { + super.onDetach(); + LogUtil.enterBlock("SupportUiListener.onDetach"); + if (callbackWrapper != null) { + callbackWrapper.successListener = null; + callbackWrapper.failureListener = null; + } + } +} + diff --git a/java/com/android/dialer/common/concurrent/UiListener.java b/java/com/android/dialer/common/concurrent/UiListener.java index b5922f9c8..a2d976f3b 100644 --- a/java/com/android/dialer/common/concurrent/UiListener.java +++ b/java/com/android/dialer/common/concurrent/UiListener.java @@ -31,6 +31,7 @@ import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; + /** * A headless fragment for use in UI components that interact with ListenableFutures. * @@ -148,3 +149,4 @@ public class UiListener<OutputT> extends Fragment { } } } + diff --git a/java/com/android/dialer/speeddial/SpeedDialFragment.java b/java/com/android/dialer/speeddial/SpeedDialFragment.java index 3cf6cb9b9..aca4886a4 100644 --- a/java/com/android/dialer/speeddial/SpeedDialFragment.java +++ b/java/com/android/dialer/speeddial/SpeedDialFragment.java @@ -18,8 +18,6 @@ package com.android.dialer.speeddial; import android.content.Intent; import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v7.widget.RecyclerView; @@ -28,6 +26,8 @@ import android.view.View; import android.view.ViewGroup; import com.android.dialer.callintent.CallInitiationType; import com.android.dialer.callintent.CallIntentBuilder; +import com.android.dialer.common.concurrent.DialerExecutorComponent; +import com.android.dialer.common.concurrent.SupportUiListener; import com.android.dialer.precall.PreCall; import com.android.dialer.speeddial.FavoritesViewHolder.FavoriteContactsListener; import com.android.dialer.speeddial.HeaderViewHolder.SpeedDialHeaderListener; @@ -35,9 +35,7 @@ import com.android.dialer.speeddial.SuggestionViewHolder.SuggestedContactsListen import com.android.dialer.speeddial.database.SpeedDialEntry.Channel; import com.android.dialer.speeddial.loader.SpeedDialUiItem; import com.android.dialer.speeddial.loader.UiItemLoaderComponent; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.collect.ImmutableList; import java.util.List; /** @@ -57,6 +55,7 @@ public class SpeedDialFragment extends Fragment { private final SuggestedContactsListener suggestedListener = new SpeedDialSuggestedListener(); private SpeedDialAdapter adapter; + private SupportUiListener<ImmutableList<SpeedDialUiItem>> speedDialLoaderListener; public static SpeedDialFragment newInstance() { return new SpeedDialFragment(); @@ -73,6 +72,10 @@ public class SpeedDialFragment extends Fragment { new SpeedDialAdapter(getContext(), favoritesListener, suggestedListener, headerListener); recyclerView.setLayoutManager(adapter.getLayoutManager(getContext())); recyclerView.setAdapter(adapter); + + speedDialLoaderListener = + DialerExecutorComponent.get(getContext()) + .createUiListener(getChildFragmentManager(), "speed_dial_loader_listener"); return view; } @@ -84,28 +87,16 @@ public class SpeedDialFragment extends Fragment { @Override public void onResume() { super.onResume(); - Futures.addCallback( - UiItemLoaderComponent.get(getContext().getApplicationContext()) - .speedDialUiItemLoader() - .loadSpeedDialUiItems(), - new FutureCallback<List<SpeedDialUiItem>>() { - @Override - public void onSuccess(List<SpeedDialUiItem> speedDialUiItems) { - // TODO(calderwoodra): this is bad - new Handler(Looper.getMainLooper()) - .post( - () -> { - adapter.setSpeedDialUiItems(speedDialUiItems); - adapter.notifyDataSetChanged(); - }); - } - - @Override - public void onFailure(Throwable throwable) { - throw new RuntimeException(throwable); - } + speedDialLoaderListener.listen( + getContext(), + UiItemLoaderComponent.get(getContext()).speedDialUiItemLoader().loadSpeedDialUiItems(), + speedDialUiItems -> { + adapter.setSpeedDialUiItems(speedDialUiItems); + adapter.notifyDataSetChanged(); }, - MoreExecutors.directExecutor()); + throwable -> { + throw new RuntimeException(throwable); + }); } private class SpeedDialFragmentHeaderListener implements SpeedDialHeaderListener { |