Skip to content

Commit 585bfff

Browse files
devlearnerStypox
authored andcommitted
Utilize a retained fragment to safekeep network requests in flight
pending result for openAddToPlaylistDialog() and openDownloadDialog() Despite marked deprecated, setRetainInstance(true) is probably our best bet (since a ViewModel is probably too overkill for our present purpose)
1 parent 0f9c20c commit 585bfff

1 file changed

Lines changed: 163 additions & 40 deletions

File tree

app/src/main/java/org/schabi/newpipe/RouterActivity.java

Lines changed: 163 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,11 @@
8383
import org.schabi.newpipe.views.FocusOverlayView;
8484

8585
import java.io.Serializable;
86+
import java.lang.ref.WeakReference;
8687
import java.util.ArrayList;
8788
import java.util.Arrays;
8889
import java.util.List;
90+
import java.util.Vector;
8991

9092
import icepick.Icepick;
9193
import icepick.State;
@@ -300,7 +302,7 @@ private static void handleError(final Context context, final ErrorInfo errorInfo
300302
}
301303
}
302304

303-
private void showUnsupportedUrlDialog(final String url) {
305+
protected void showUnsupportedUrlDialog(final String url) {
304306
final Context context = getThemeWrapperContext();
305307
new AlertDialog.Builder(context)
306308
.setTitle(R.string.unsupported_url)
@@ -587,7 +589,7 @@ private List<AdapterChoiceItem> getChoicesForService(final StreamingService serv
587589
return returnedItems;
588590
}
589591

590-
private Context getThemeWrapperContext() {
592+
protected Context getThemeWrapperContext() {
591593
return new ContextThemeWrapper(this, ThemeHelper.isLightThemeSelected(this)
592594
? R.style.LightTheme : R.style.DarkTheme);
593595
}
@@ -694,54 +696,175 @@ private boolean canHandleChoiceLikeShowInfo(final String selectedChoiceKey) {
694696
return playerType == null || playerType == PlayerType.MAIN;
695697
}
696698

699+
public static class PersistentFragment extends Fragment {
700+
private WeakReference<AppCompatActivity> context;
701+
private boolean isPaused = true;
702+
private final Vector<ResultRunnable> buffer = new Vector<>();
703+
private final CompositeDisposable disposables = new CompositeDisposable();
704+
705+
public interface ResultRunnable {
706+
void run(AppCompatActivity context);
707+
}
708+
709+
@Override
710+
public void onAttach(@NonNull final Context activityContext) {
711+
super.onAttach(activityContext);
712+
context = new WeakReference<>((AppCompatActivity) activityContext);
713+
}
714+
715+
@SuppressWarnings("deprecation")
716+
@Override
717+
public void onCreate(final Bundle savedInstanceState) {
718+
super.onCreate(savedInstanceState);
719+
setRetainInstance(true);
720+
}
721+
722+
@Override
723+
public void onDestroy() {
724+
super.onDestroy();
725+
disposables.clear();
726+
}
727+
728+
@Override
729+
public void onPause() {
730+
isPaused = true;
731+
super.onPause();
732+
}
733+
734+
@Override
735+
public void onResume() {
736+
isPaused = false;
737+
playback();
738+
super.onResume();
739+
}
740+
741+
private AppCompatActivity getActivityContext() {
742+
return context == null ? null : context.get();
743+
}
744+
745+
// guard against IllegalStateException in calling DialogFragment.show() whilst in background
746+
// (which could happen, say, when the user pressed the home button while waiting for
747+
// the network request to return) when it internally calls FragmentTransaction.commit()
748+
// after the FragmentManager has saved its states (isStateSaved() == true)
749+
// (ref: https://stackoverflow.com/a/39813506)
750+
private void playback() {
751+
if (activityGone()) {
752+
done();
753+
}
754+
if (buffer.size() == 0 || isPaused) {
755+
return;
756+
}
757+
while (buffer.size() > 0) {
758+
final ResultRunnable runnable = buffer.elementAt(0);
759+
buffer.removeElementAt(0);
760+
getActivityContext().runOnUiThread(() -> {
761+
// execute queued task with new context, in case activity has been recreated
762+
runnable.run(getActivityContext());
763+
});
764+
}
765+
done();
766+
}
767+
private boolean activityGone() {
768+
return getActivityContext() == null || getActivityContext().isFinishing();
769+
}
770+
771+
// a DefaultLifecycleObserver is probably a good candidate here, but for now
772+
// let's stick with a vanilla approach to avoid pulling in an extra artifact just for this
773+
private void runOnVisible(final ResultRunnable runnable) {
774+
if (activityGone()) {
775+
done();
776+
}
777+
if (isPaused) {
778+
buffer.add(runnable);
779+
if (!getActivityContext().isChangingConfigurations()) {
780+
// try to bring the activity back to front if minimised
781+
final Intent i = new Intent(getActivityContext(), RouterActivity.class);
782+
i.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
783+
startActivity(i);
784+
}
785+
} else {
786+
getActivityContext().runOnUiThread(() -> {
787+
runnable.run(getActivityContext());
788+
done();
789+
});
790+
}
791+
}
792+
793+
private void done() {
794+
if (getActivityContext() != null) {
795+
getActivityContext().getSupportFragmentManager()
796+
.beginTransaction().remove(this).commit();
797+
}
798+
}
799+
800+
@SuppressLint("CheckResult")
801+
protected void openDownloadDialog(final int currentServiceId, final String currentUrl) {
802+
disposables.add(ExtractorHelper.getStreamInfo(currentServiceId, currentUrl, true)
803+
.subscribeOn(Schedulers.io())
804+
.observeOn(AndroidSchedulers.mainThread())
805+
.subscribe(result ->
806+
runOnVisible(ctx -> {
807+
final FragmentManager fm = ctx.getSupportFragmentManager();
808+
final DownloadDialog downloadDialog = new DownloadDialog(ctx, result);
809+
// dismiss listener to be handled by FragmentManager
810+
downloadDialog.show(fm, "downloadDialog");
811+
}
812+
), throwable -> runOnVisible(ctx ->
813+
((RouterActivity) ctx).showUnsupportedUrlDialog(currentUrl))));
814+
}
815+
816+
private void openAddToPlaylistDialog(final int currentServiceId, final String currentUrl) {
817+
disposables.add(ExtractorHelper.getStreamInfo(currentServiceId, currentUrl, false)
818+
.subscribeOn(Schedulers.io())
819+
.observeOn(AndroidSchedulers.mainThread())
820+
.subscribe(
821+
info -> runOnVisible(ctx ->
822+
PlaylistDialog.createCorrespondingDialog(
823+
((RouterActivity) ctx).getThemeWrapperContext(),
824+
List.of(new StreamEntity(info)),
825+
playlistDialog -> {
826+
// dismiss listener to be handled by FragmentManager
827+
final FragmentManager fm = ctx.getSupportFragmentManager();
828+
playlistDialog.show(fm, "addToPlaylistDialog");
829+
}
830+
)),
831+
throwable -> runOnVisible(ctx -> handleError(ctx, new ErrorInfo(
832+
throwable,
833+
UserAction.REQUESTED_STREAM,
834+
"Tried to add " + currentUrl + " to a playlist",
835+
((RouterActivity) ctx).currentService.getServiceId())
836+
))
837+
)
838+
);
839+
}
840+
}
841+
697842
private void openAddToPlaylistDialog() {
698843
pleaseWait();
699844

700-
disposables.add(ExtractorHelper.getStreamInfo(currentServiceId, currentUrl, false)
701-
.subscribeOn(Schedulers.io())
702-
.observeOn(AndroidSchedulers.mainThread())
703-
.subscribe(
704-
info -> PlaylistDialog.createCorrespondingDialog(
705-
getThemeWrapperContext(),
706-
List.of(new StreamEntity(info)),
707-
playlistDialog -> {
708-
// to be handled by FragmentManager
709-
// playlistDialog.setOnDismissListener(dialog ->finish());
710-
711-
final FragmentManager fm = getSupportFragmentManager();
712-
playlistDialog.show(fm, "addToPlaylistDialog");
713-
fm.executePendingTransactions();
714-
}
715-
),
716-
throwable -> handleError(this, new ErrorInfo(
717-
throwable,
718-
UserAction.REQUESTED_STREAM,
719-
"Tried to add " + currentUrl + " to a playlist",
720-
currentService.getServiceId())
721-
)
722-
)
723-
);
845+
getPersistFragment().openAddToPlaylistDialog(currentServiceId, currentUrl);
724846
}
725847

726-
@SuppressLint("CheckResult")
727848
private void openDownloadDialog() {
728849
pleaseWait();
729850

730-
disposables.add(ExtractorHelper.getStreamInfo(currentServiceId, currentUrl, true)
731-
.subscribeOn(Schedulers.io())
732-
.observeOn(AndroidSchedulers.mainThread())
733-
.subscribe(result -> {
734-
final DownloadDialog downloadDialog = new DownloadDialog(this, result);
735-
// to be handled by FragmentManager since listener would be gone when recreated
736-
// playlistDialog.setOnDismissListener(dialog ->finish());
737-
738-
final FragmentManager fm = getSupportFragmentManager();
739-
downloadDialog.show(fm, "downloadDialog");
740-
fm.executePendingTransactions();
741-
}, throwable -> showUnsupportedUrlDialog(currentUrl)));
851+
getPersistFragment().openDownloadDialog(currentServiceId, currentUrl);
852+
}
853+
854+
private PersistentFragment getPersistFragment() {
855+
final FragmentManager fm = getSupportFragmentManager();
856+
PersistentFragment persistFragment =
857+
(PersistentFragment) fm.findFragmentByTag("PERSIST_FRAGMENT");
858+
if (persistFragment == null) {
859+
persistFragment = new PersistentFragment();
860+
fm.beginTransaction()
861+
.add(persistFragment, "PERSIST_FRAGMENT")
862+
.commitNow();
863+
}
864+
return persistFragment;
742865
}
743866

744-
private void pleaseWait() {
867+
protected void pleaseWait() {
745868
// Getting the stream info usually takes a moment
746869
// Notifying the user here to ensure that no confusion arises
747870
Toast.makeText(

0 commit comments

Comments
 (0)