9191import java .util .ArrayList ;
9292import java .util .Arrays ;
9393import java .util .List ;
94+ import java .util .Optional ;
95+ import java .util .function .Consumer ;
9496
9597import icepick .Icepick ;
9698import icepick .State ;
9799import io .reactivex .rxjava3 .android .schedulers .AndroidSchedulers ;
98100import io .reactivex .rxjava3 .core .Observable ;
99101import io .reactivex .rxjava3 .core .Single ;
100- import io .reactivex .rxjava3 .core .SingleTransformer ;
101102import io .reactivex .rxjava3 .disposables .CompositeDisposable ;
102103import io .reactivex .rxjava3 .disposables .Disposable ;
103- import io .reactivex .rxjava3 .functions .Consumer ;
104104import io .reactivex .rxjava3 .schedulers .Schedulers ;
105105
106106/**
@@ -702,7 +702,7 @@ private boolean canHandleChoiceLikeShowInfo(final String selectedChoiceKey) {
702702 }
703703
704704 public static class PersistentFragment extends Fragment {
705- private WeakReference <AppCompatActivity > context ;
705+ private WeakReference <AppCompatActivity > weakContext ;
706706 private final CompositeDisposable disposables = new CompositeDisposable ();
707707 private int running = 0 ;
708708
@@ -711,21 +711,23 @@ private synchronized void inFlight(final boolean started) {
711711 running ++;
712712 } else {
713713 running --;
714- if (running <= 0 && getActivityContext () != null ) {
715- getActivityContext ().getSupportFragmentManager ()
716- .beginTransaction ().remove (this ).commit ();
714+ if (running <= 0 ) {
715+ getActivityContext ().ifPresent ( context -> context . getSupportFragmentManager ()
716+ .beginTransaction ().remove (this ).commit ()) ;
717717 }
718718 }
719719 }
720720
721- public interface ResultRunnable {
722- void run (AppCompatActivity context );
723- }
724-
725721 @ Override
726722 public void onAttach (@ NonNull final Context activityContext ) {
727723 super .onAttach (activityContext );
728- context = new WeakReference <>((AppCompatActivity ) activityContext );
724+ weakContext = new WeakReference <>((AppCompatActivity ) activityContext );
725+ }
726+
727+ @ Override
728+ public void onDetach () {
729+ super .onDetach ();
730+ weakContext = null ;
729731 }
730732
731733 @ SuppressWarnings ("deprecation" )
@@ -741,73 +743,70 @@ public void onDestroy() {
741743 disposables .clear ();
742744 }
743745
744- private AppCompatActivity getActivityContext () {
745- return context == null ? null : context .get ();
746- }
747-
748- private boolean activityGone () {
749- return getActivityContext () == null || getActivityContext ().isFinishing ();
746+ /**
747+ * @return the activity context, if there is one and the activity is not finishing
748+ */
749+ private Optional <AppCompatActivity > getActivityContext () {
750+ return Optional .ofNullable (weakContext )
751+ .flatMap (context -> Optional .ofNullable (context .get ()))
752+ .filter (context -> !context .isFinishing ());
750753 }
751754
752755 // guard against IllegalStateException in calling DialogFragment.show() whilst in background
753756 // (which could happen, say, when the user pressed the home button while waiting for
754757 // the network request to return) when it internally calls FragmentTransaction.commit()
755758 // after the FragmentManager has saved its states (isStateSaved() == true)
756759 // (ref: https://stackoverflow.com/a/39813506)
757- private void runOnVisible (final ResultRunnable runnable ) {
758- if (activityGone ()) {
759- inFlight (false );
760- return ;
761- }
762- if (getLifecycle ().getCurrentState ().isAtLeast (Lifecycle .State .STARTED )) {
763- getActivityContext ().runOnUiThread (() -> {
764- runnable .run (getActivityContext ());
765- inFlight (false );
766- });
767- } else {
768- getLifecycle ().addObserver (new DefaultLifecycleObserver () {
769- @ Override
770- public void onResume (@ NonNull final LifecycleOwner owner ) {
771- getLifecycle ().removeObserver (this );
772- if (activityGone ()) {
773- inFlight (false );
774- return ;
760+ private void runOnVisible (final Consumer <AppCompatActivity > runnable ) {
761+ getActivityContext ().ifPresentOrElse (context -> {
762+ if (getLifecycle ().getCurrentState ().isAtLeast (Lifecycle .State .STARTED )) {
763+ context .runOnUiThread (() -> {
764+ runnable .accept (context );
765+ inFlight (false );
766+ });
767+ } else {
768+ getLifecycle ().addObserver (new DefaultLifecycleObserver () {
769+ @ Override
770+ public void onResume (@ NonNull final LifecycleOwner owner ) {
771+ getLifecycle ().removeObserver (this );
772+ getActivityContext ().ifPresentOrElse (context ->
773+ context .runOnUiThread (() -> {
774+ runnable .accept (context );
775+ inFlight (false );
776+ }),
777+ () -> inFlight (false )
778+ );
775779 }
776- getActivityContext ().runOnUiThread (() -> {
777- runnable .run (getActivityContext ());
778- inFlight (false );
779- });
780+ });
781+ // this trick doesn't seem to work on Android 10+ (API 29)
782+ // which places restrictions on starting activities from the background
783+ if (Build .VERSION .SDK_INT < Build .VERSION_CODES .Q
784+ && !context .isChangingConfigurations ()) {
785+ // try to bring the activity back to front if minimised
786+ final Intent i = new Intent (context , RouterActivity .class );
787+ i .setFlags (Intent .FLAG_ACTIVITY_REORDER_TO_FRONT );
788+ startActivity (i );
780789 }
781- });
782- // this trick doesn't seem to work on Android 10+ (API 29)
783- // which places restrictions on starting activities from the background
784- if (Build .VERSION .SDK_INT < Build .VERSION_CODES .Q
785- && !getActivityContext ().isChangingConfigurations ()) {
786- // try to bring the activity back to front if minimised
787- final Intent i = new Intent (getActivityContext (), RouterActivity .class );
788- i .setFlags (Intent .FLAG_ACTIVITY_REORDER_TO_FRONT );
789- startActivity (i );
790790 }
791- }
791+
792+ }, () -> {
793+ // this branch is executed if there is no activity context
794+ inFlight (false );
795+ });
792796 }
793797
794- <T > SingleTransformer <T , T > pleaseWait () {
795- return single -> single
796- // 'abuse' ambWith() here to cancel the toast for us when the wait is over
797- .ambWith (Single .create (emitter -> {
798- if (!activityGone ()) {
799- getActivityContext ().runOnUiThread (() -> {
800- // Getting the stream info usually takes a moment
801- // Notifying the user here to ensure that no confusion arises
802- final Toast t = Toast .makeText (
803- getActivityContext ().getApplicationContext (),
804- getString (R .string .processing_may_take_a_moment ),
805- Toast .LENGTH_LONG );
806- t .show ();
807- emitter .setCancellable (t ::cancel );
808- });
809- }
810- }));
798+ <T > Single <T > pleaseWait (final Single <T > single ) {
799+ // 'abuse' ambWith() here to cancel the toast for us when the wait is over
800+ return single .ambWith (Single .create (emitter -> getActivityContext ().ifPresent (context ->
801+ context .runOnUiThread (() -> {
802+ // Getting the stream info usually takes a moment
803+ // Notifying the user here to ensure that no confusion arises
804+ final Toast toast = Toast .makeText (context ,
805+ getString (R .string .processing_may_take_a_moment ),
806+ Toast .LENGTH_LONG );
807+ toast .show ();
808+ emitter .setCancellable (toast ::cancel );
809+ }))));
811810 }
812811
813812 @ SuppressLint ("CheckResult" )
@@ -816,7 +815,7 @@ private void openDownloadDialog(final int currentServiceId, final String current
816815 disposables .add (ExtractorHelper .getStreamInfo (currentServiceId , currentUrl , true )
817816 .subscribeOn (Schedulers .io ())
818817 .observeOn (AndroidSchedulers .mainThread ())
819- .compose (pleaseWait () )
818+ .compose (this :: pleaseWait )
820819 .subscribe (result ->
821820 runOnVisible (ctx -> {
822821 final FragmentManager fm = ctx .getSupportFragmentManager ();
@@ -833,23 +832,18 @@ private void openAddToPlaylistDialog(final int currentServiceId, final String cu
833832 disposables .add (ExtractorHelper .getStreamInfo (currentServiceId , currentUrl , false )
834833 .subscribeOn (Schedulers .io ())
835834 .observeOn (AndroidSchedulers .mainThread ())
836- .compose (pleaseWait () )
835+ .compose (this :: pleaseWait )
837836 .subscribe (
838- info -> {
839- if (!activityGone ()) {
840- PlaylistDialog .createCorrespondingDialog (
841- getActivityContext (),
842- List .of (new StreamEntity (info )),
843- playlistDialog ->
844- runOnVisible (ctx -> {
845- // dismiss listener to be handled by FragmentManager
846- final FragmentManager fm =
847- ctx .getSupportFragmentManager ();
848- playlistDialog .show (fm , "addToPlaylistDialog" );
849- })
850- );
851- }
852- },
837+ info -> getActivityContext ().ifPresent (context ->
838+ PlaylistDialog .createCorrespondingDialog (context ,
839+ List .of (new StreamEntity (info )),
840+ playlistDialog -> runOnVisible (ctx -> {
841+ // dismiss listener to be handled by FragmentManager
842+ final FragmentManager fm =
843+ ctx .getSupportFragmentManager ();
844+ playlistDialog .show (fm , "addToPlaylistDialog" );
845+ })
846+ )),
853847 throwable -> runOnVisible (ctx -> handleError (ctx , new ErrorInfo (
854848 throwable ,
855849 UserAction .REQUESTED_STREAM ,
0 commit comments