Skip to content

Commit 5f55d7d

Browse files
committed
Fix NullPointerException in enqueue actions by using Application Context
Use getApplicationContext() instead of getContext() in ENQUEUE, ENQUEUE_NEXT, START_HERE_ON_BACKGROUND, and START_HERE_ON_POPUP entries to prevent NullPointerException when the fragment's activity context becomes null during configuration changes (e.g. device rotation). The fragment context can become null when the activity is destroyed during rotation, but the async callback from fetchItemInfoIfSparse still holds a reference to the now-detached fragment. Using Application context ensures a stable, non-null context throughout the async operation lifecycle.
1 parent 515bb6e commit 5f55d7d

File tree

1 file changed

+134
-132
lines changed

1 file changed

+134
-132
lines changed

app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java

Lines changed: 134 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import static org.schabi.newpipe.util.SparseItemUtil.fetchStreamInfoAndSaveToDatabase;
66
import static org.schabi.newpipe.util.SparseItemUtil.fetchUploaderUrlIfSparse;
77

8+
import android.content.Context;
89
import android.net.Uri;
910

1011
import androidx.annotation.NonNull;
@@ -29,142 +30,143 @@
2930

3031
/**
3132
* <p>
32-
* This enum provides entries that are accepted
33-
* by the {@link InfoItemDialog.Builder}.
33+
* This enum provides entries that are accepted
34+
* by the {@link InfoItemDialog.Builder}.
3435
* </p>
3536
* <p>
36-
* These entries contain a String {@link #resource} which is displayed in the dialog and
37-
* a default {@link #action} that is executed
38-
* when the entry is selected (via <code>onClick()</code>).
39-
* <br/>
40-
* They action can be overridden by using the Builder's
41-
* {@link InfoItemDialog.Builder#setAction(
42-
* StreamDialogDefaultEntry, StreamDialogEntry.StreamDialogEntryAction)}
43-
* method.
37+
* These entries contain a String {@link #resource} which is displayed in the
38+
* dialog and
39+
* a default {@link #action} that is executed
40+
* when the entry is selected (via <code>onClick()</code>).
41+
* <br/>
42+
* They action can be overridden by using the Builder's
43+
* {@link InfoItemDialog.Builder#setAction(
44+
* StreamDialogDefaultEntry, StreamDialogEntry.StreamDialogEntryAction)}
45+
* method.
4446
* </p>
4547
*/
4648
public enum StreamDialogDefaultEntry {
47-
SHOW_CHANNEL_DETAILS(R.string.show_channel_details, (fragment, item) ->
48-
fetchUploaderUrlIfSparse(fragment.requireContext(), item.getServiceId(), item.getUrl(),
49-
item.getUploaderUrl(), url -> openChannelFragment(fragment, item, url))
50-
),
51-
52-
/**
53-
* Enqueues the stream automatically to the current PlayerType.
54-
*/
55-
ENQUEUE(R.string.enqueue_stream, (fragment, item) ->
56-
fetchItemInfoIfSparse(fragment.requireContext(), item, singlePlayQueue ->
57-
NavigationHelper.enqueueOnPlayer(fragment.getContext(), singlePlayQueue))
58-
),
59-
60-
/**
61-
* Enqueues the stream automatically to the current PlayerType
62-
* after the currently playing stream.
63-
*/
64-
ENQUEUE_NEXT(R.string.enqueue_next_stream, (fragment, item) ->
65-
fetchItemInfoIfSparse(fragment.requireContext(), item, singlePlayQueue ->
66-
NavigationHelper.enqueueNextOnPlayer(fragment.getContext(), singlePlayQueue))
67-
),
68-
69-
START_HERE_ON_BACKGROUND(R.string.start_here_on_background, (fragment, item) ->
70-
fetchItemInfoIfSparse(fragment.requireContext(), item, singlePlayQueue ->
71-
NavigationHelper.playOnBackgroundPlayer(
72-
fragment.getContext(), singlePlayQueue, true))),
73-
74-
START_HERE_ON_POPUP(R.string.start_here_on_popup, (fragment, item) ->
75-
fetchItemInfoIfSparse(fragment.requireContext(), item, singlePlayQueue ->
76-
NavigationHelper.playOnPopupPlayer(fragment.getContext(), singlePlayQueue, true))),
77-
78-
SET_AS_PLAYLIST_THUMBNAIL(R.string.set_as_playlist_thumbnail, (fragment, item) -> {
79-
throw new UnsupportedOperationException("This needs to be implemented manually "
80-
+ "by using InfoItemDialog.Builder.setAction()");
81-
}),
82-
83-
DELETE(R.string.delete, (fragment, item) -> {
84-
throw new UnsupportedOperationException("This needs to be implemented manually "
85-
+ "by using InfoItemDialog.Builder.setAction()");
86-
}),
87-
88-
/**
89-
* Opens a {@link PlaylistDialog} to either append the stream to a playlist
90-
* or create a new playlist if there are no local playlists.
91-
*/
92-
APPEND_PLAYLIST(R.string.add_to_playlist, (fragment, item) ->
93-
PlaylistDialog.createCorrespondingDialog(
94-
fragment.getContext(),
95-
List.of(new StreamEntity(item)),
96-
dialog -> dialog.show(
97-
fragment.getParentFragmentManager(),
98-
"StreamDialogEntry@"
99-
+ (dialog instanceof PlaylistAppendDialog ? "append" : "create")
100-
+ "_playlist"
101-
)
102-
)
103-
),
104-
105-
PLAY_WITH_KODI(R.string.play_with_kodi_title, (fragment, item) ->
106-
KoreUtils.playWithKore(fragment.requireContext(), Uri.parse(item.getUrl()))),
107-
108-
SHARE(R.string.share, (fragment, item) ->
109-
ShareUtils.shareText(fragment.requireContext(), item.getName(), item.getUrl(),
110-
item.getThumbnails())),
111-
112-
/**
113-
* Opens a {@link DownloadDialog} after fetching some stream info.
114-
* If the user quits the current fragment, it will not open a DownloadDialog.
115-
*/
116-
DOWNLOAD(R.string.download, (fragment, item) ->
117-
fetchStreamInfoAndSaveToDatabase(fragment.requireContext(), item.getServiceId(),
118-
item.getUrl(), info -> {
119-
// Ensure the fragment is attached and its state hasn't been saved to avoid
120-
// showing dialog during lifecycle changes or when the activity is paused,
121-
// e.g. by selecting the download option and opening a different fragment.
122-
if (fragment.isAdded() && !fragment.isStateSaved()) {
123-
final DownloadDialog downloadDialog =
124-
new DownloadDialog(fragment.requireContext(), info);
125-
downloadDialog.show(fragment.getChildFragmentManager(),
126-
"downloadDialog");
127-
}
128-
})
129-
),
130-
131-
OPEN_IN_BROWSER(R.string.open_in_browser, (fragment, item) ->
132-
ShareUtils.openUrlInBrowser(fragment.requireContext(), item.getUrl())),
133-
134-
135-
MARK_AS_WATCHED(R.string.mark_as_watched, (fragment, item) ->
136-
new HistoryRecordManager(fragment.getContext())
137-
.markAsWatched(item)
138-
.doOnError(error -> {
139-
ErrorUtil.showSnackbar(
140-
fragment.requireContext(),
141-
new ErrorInfo(
142-
error,
143-
UserAction.OPEN_INFO_ITEM_DIALOG,
144-
"Got an error when trying to mark as watched"
145-
)
146-
);
147-
})
148-
.onErrorComplete()
149-
.observeOn(AndroidSchedulers.mainThread())
150-
.subscribe()
151-
);
152-
153-
154-
@StringRes
155-
public final int resource;
156-
@NonNull
157-
public final StreamDialogEntry.StreamDialogEntryAction action;
158-
159-
StreamDialogDefaultEntry(@StringRes final int resource,
160-
@NonNull final StreamDialogEntry.StreamDialogEntryAction action) {
161-
this.resource = resource;
162-
this.action = action;
163-
}
164-
165-
@NonNull
166-
public StreamDialogEntry toStreamDialogEntry() {
167-
return new StreamDialogEntry(resource, action);
168-
}
49+
SHOW_CHANNEL_DETAILS(R.string.show_channel_details,
50+
(fragment, item) -> fetchUploaderUrlIfSparse(fragment.requireContext(), item.getServiceId(),
51+
item.getUrl(),
52+
item.getUploaderUrl(), url -> openChannelFragment(fragment, item, url))),
53+
54+
/**
55+
* Enqueues the stream automatically to the current PlayerType.
56+
*/
57+
ENQUEUE(R.string.enqueue_stream, (fragment, item) -> {
58+
final Context ctx = fragment.requireContext().getApplicationContext();
59+
fetchItemInfoIfSparse(ctx, item,
60+
singlePlayQueue -> NavigationHelper.enqueueOnPlayer(ctx, singlePlayQueue));
61+
}),
62+
63+
/**
64+
* Enqueues the stream automatically to the current PlayerType
65+
* after the currently playing stream.
66+
*/
67+
ENQUEUE_NEXT(R.string.enqueue_next_stream, (fragment, item) -> {
68+
final Context ctx = fragment.requireContext().getApplicationContext();
69+
fetchItemInfoIfSparse(ctx, item,
70+
singlePlayQueue -> NavigationHelper.enqueueNextOnPlayer(ctx, singlePlayQueue));
71+
}),
72+
73+
START_HERE_ON_BACKGROUND(R.string.start_here_on_background, (fragment, item) -> {
74+
final Context ctx = fragment.requireContext().getApplicationContext();
75+
fetchItemInfoIfSparse(ctx, item,
76+
singlePlayQueue -> NavigationHelper.playOnBackgroundPlayer(ctx, singlePlayQueue, true));
77+
}),
78+
79+
START_HERE_ON_POPUP(R.string.start_here_on_popup, (fragment, item) -> {
80+
final Context ctx = fragment.requireContext().getApplicationContext();
81+
fetchItemInfoIfSparse(ctx, item,
82+
singlePlayQueue -> NavigationHelper.playOnPopupPlayer(ctx, singlePlayQueue, true));
83+
}),
84+
85+
SET_AS_PLAYLIST_THUMBNAIL(R.string.set_as_playlist_thumbnail, (fragment, item) -> {
86+
throw new UnsupportedOperationException("This needs to be implemented manually "
87+
+ "by using InfoItemDialog.Builder.setAction()");
88+
}),
89+
90+
DELETE(R.string.delete, (fragment, item) -> {
91+
throw new UnsupportedOperationException("This needs to be implemented manually "
92+
+ "by using InfoItemDialog.Builder.setAction()");
93+
}),
94+
95+
/**
96+
* Opens a {@link PlaylistDialog} to either append the stream to a playlist
97+
* or create a new playlist if there are no local playlists.
98+
*/
99+
APPEND_PLAYLIST(R.string.add_to_playlist, (fragment, item) -> PlaylistDialog.createCorrespondingDialog(
100+
fragment.getContext(),
101+
List.of(new StreamEntity(item)),
102+
dialog -> dialog.show(
103+
fragment.getParentFragmentManager(),
104+
"StreamDialogEntry@"
105+
+ (dialog instanceof PlaylistAppendDialog ? "append" : "create")
106+
+ "_playlist"))),
107+
108+
PLAY_WITH_KODI(R.string.play_with_kodi_title,
109+
(fragment, item) -> KoreUtils.playWithKore(fragment.requireContext(),
110+
Uri.parse(item.getUrl()))),
111+
112+
SHARE(R.string.share,
113+
(fragment, item) -> ShareUtils.shareText(fragment.requireContext(), item.getName(),
114+
item.getUrl(),
115+
item.getThumbnails())),
116+
117+
/**
118+
* Opens a {@link DownloadDialog} after fetching some stream info.
119+
* If the user quits the current fragment, it will not open a DownloadDialog.
120+
*/
121+
DOWNLOAD(R.string.download,
122+
(fragment, item) -> fetchStreamInfoAndSaveToDatabase(fragment.requireContext(),
123+
item.getServiceId(),
124+
item.getUrl(), info -> {
125+
// Ensure the fragment is attached and its state hasn't been saved to
126+
// avoid
127+
// showing dialog during lifecycle changes or when the activity is
128+
// paused,
129+
// e.g. by selecting the download option and opening a different
130+
// fragment.
131+
if (fragment.isAdded() && !fragment.isStateSaved()) {
132+
final DownloadDialog downloadDialog = new DownloadDialog(
133+
fragment.requireContext(), info);
134+
downloadDialog.show(fragment.getChildFragmentManager(),
135+
"downloadDialog");
136+
}
137+
})),
138+
139+
OPEN_IN_BROWSER(R.string.open_in_browser,
140+
(fragment, item) -> ShareUtils.openUrlInBrowser(fragment.requireContext(), item.getUrl())),
141+
142+
MARK_AS_WATCHED(R.string.mark_as_watched, (fragment, item) -> new HistoryRecordManager(fragment.getContext())
143+
.markAsWatched(item)
144+
.doOnError(error -> {
145+
ErrorUtil.showSnackbar(
146+
fragment.requireContext(),
147+
new ErrorInfo(
148+
error,
149+
UserAction.OPEN_INFO_ITEM_DIALOG,
150+
"Got an error when trying to mark as watched"));
151+
})
152+
.onErrorComplete()
153+
.observeOn(AndroidSchedulers.mainThread())
154+
.subscribe());
155+
156+
@StringRes
157+
public final int resource;
158+
@NonNull
159+
public final StreamDialogEntry.StreamDialogEntryAction action;
160+
161+
StreamDialogDefaultEntry(@StringRes final int resource,
162+
@NonNull final StreamDialogEntry.StreamDialogEntryAction action) {
163+
this.resource = resource;
164+
this.action = action;
165+
}
166+
167+
@NonNull
168+
public StreamDialogEntry toStreamDialogEntry() {
169+
return new StreamDialogEntry(resource, action);
170+
}
169171

170172
}

0 commit comments

Comments
 (0)