2022-11-04 20:10:26 +01:00
|
|
|
[versions]
|
2024-08-11 16:24:47 +02:00
|
|
|
agp = "8.5.2"
|
2024-08-02 17:12:49 +02:00
|
|
|
androidx-activity = "1.9.1"
|
2024-05-31 13:44:03 +02:00
|
|
|
androidx-appcompat = "1.7.0"
|
2024-03-09 13:32:42 +01:00
|
|
|
androidx-browser = "1.8.0"
|
2022-11-04 20:10:26 +01:00
|
|
|
androidx-cardview = "1.0.0"
|
|
|
|
androidx-constraintlayout = "2.1.4"
|
2024-05-03 13:46:39 +02:00
|
|
|
androidx-core = "1.13.1"
|
2024-05-10 17:14:26 +02:00
|
|
|
androidx-drawerlayout = "1.2.0"
|
2024-01-05 19:53:33 +01:00
|
|
|
androidx-exifinterface = "1.3.7"
|
2024-08-02 17:12:39 +02:00
|
|
|
androidx-fragment = "1.8.2"
|
2024-05-10 15:55:07 +02:00
|
|
|
androidx-hilt = "1.2.0"
|
2024-07-03 19:53:25 +02:00
|
|
|
androidx-junit = "1.2.1"
|
2024-08-02 18:47:00 +02:00
|
|
|
androidx-lifecycle = "2.8.4"
|
fix(deps): update androidx.media3 to v1.4.0 (#4587)
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)
This PR contains the following updates:
| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [androidx.media3:media3-ui](https://togithub.com/androidx/media) |
`1.3.1` -> `1.4.0` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.media3:media3-ui/1.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/androidx.media3:media3-ui/1.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/androidx.media3:media3-ui/1.3.1/1.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.media3:media3-ui/1.3.1/1.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[androidx.media3:media3-datasource-okhttp](https://togithub.com/androidx/media)
| `1.3.1` -> `1.4.0` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.media3:media3-datasource-okhttp/1.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/androidx.media3:media3-datasource-okhttp/1.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/androidx.media3:media3-datasource-okhttp/1.3.1/1.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.media3:media3-datasource-okhttp/1.3.1/1.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[androidx.media3:media3-exoplayer](https://togithub.com/androidx/media)
| `1.3.1` -> `1.4.0` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.media3:media3-exoplayer/1.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/androidx.media3:media3-exoplayer/1.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/androidx.media3:media3-exoplayer/1.3.1/1.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.media3:media3-exoplayer/1.3.1/1.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
---
### Release Notes
<details>
<summary>androidx/media (androidx.media3:media3-ui)</summary>
###
[`v1.4.0`](https://togithub.com/androidx/media/blob/HEAD/RELEASENOTES.md#140-2024-07-24)
[Compare
Source](https://togithub.com/androidx/media/compare/1.3.1...1.4.0)
This release includes the following changes since the
[1.3.1 release](#​131-2024-04-11):
- Common Library:
- Forward presumed no-op seek calls to the protected
`BasePlayer.seekTo()`
and `SimpleBasePlayer.handleSeek()` methods instead of ignoring them. If
you are implementing these methods in a custom player, you may need to
handle these additional calls with `mediaItemIndex == C.INDEX_UNSET`.
- Remove compile dependency on enhanced Java 8 desugaring
([#​1312](https://togithub.com/androidx/media/issues/1312)).
- Ensure the duration passed to `MediaItem.Builder.setImageDurationMs()`
is ignored for a non-image `MediaItem` (as documented).
- Add `Format.customData` to store app-provided custom information about
`Format` instances.
- ExoPlayer:
- Add `BasePreloadManager` which coordinates the preloading for multiple
sources based on the priorities defined by their `rankingData`.
Customization is possible by extending this class. Add
`DefaultPreloadManager` which uses `PreloadMediaSource` to preload media
samples of the sources into memory, and uses an integer `rankingData`
that indicates the index of an item on the UI.
- Add `PlayerId` to most methods of `LoadControl` to enable
`LoadControl`
implementations to support multiple players.
- Remove `Buffer.isDecodeOnly()` and `C.BUFFER_FLAG_DECODE_ONLY`. There
is
no need to set this flag as renderers and decoders will decide to skip
buffers based on timestamp. Custom `Renderer` implementations should
check if the buffer time is at least
`BaseRenderer.getLastResetPositionUs()` to decide whether a sample
should be shown. Custom `SimpleDecoder` implementations can check
`isAtLeastOutputStartTimeUs()` if needed or mark other buffers with
`DecoderOutputBuffer.shouldBeSkipped` to skip them.
- Allow a null value to be returned by
`TargetPreloadStatusControl.getTargetPreloadStatus(T)` to indicate not
to preload a `MediaSource` with the given `rankingData`.
- Add `remove(MediaSource)` to `BasePreloadManager`.
- Add `reset()` to `BasePreloadManager` to release all the holding
sources
while keep the preload manager instance.
- Add `ExoPlayer.setPriority()` (and `Builder.setPriority()`) to define
the priority value used in `PriorityTaskManager` and for MediaCodec
importance from API 35.
- Fix issue with updating the last rebuffer time which resulted in
incorrect `bs` (buffer starvation) key in CMCD
([#​1124](https://togithub.com/androidx/media/issues/1124)).
- Add
`PreloadMediaSource.PreloadControl.onLoadedToTheEndOfSource(PreloadMediaSource)`
to indicate that the source has loaded to the end. This allows the
`DefaultPreloadManager` and the custom
`PreloadMediaSource.PreloadControl` implementations to preload the next
source or take other actions.
- Fix bug where silence skipping at the end of items can trigger a
playback exception.
- Add `clear` to `PreloadMediaSource` to discard the preloading period.
- Add new error code
`PlaybackException.ERROR_CODE_DECODING_RESOURCES_RECLAIMED` that is used
when codec resources are reclaimed for higher priority tasks.
- Let `AdsMediaSource` load preroll ads before initial content media
preparation completes
([#​1358](https://togithub.com/androidx/media/issues/1358)).
- Fix bug where playback moved to `STATE_ENDED` when re-preparing a
multi-period DASH live stream after the original period was already
removed from the manifest.
- Rename `onTimelineRefreshed()` to `onSourcePrepared()` and
`onPrepared()` to `onTracksSelected()` in
`PreloadMediaSource.PreloadControl`. Also rename the IntDefs in
`DefaultPreloadManager.Stage` accordingly.
- Add experimental support for dynamic scheduling to better align work
with CPU wake-cycles and delay waking up to when renderers can progress.
You can enable this using `experimentalSetDynamicSchedulingEnabled()`
when setting up your ExoPlayer instance.
- Add `Renderer.getDurationToProgressUs()`. A `Renderer` can implement
this method to return to ExoPlayer the duration that playback must
advance for the renderer to progress. If `ExoPlayer` is set with
`experimentalSetDynamicSchedulingEnabled()` then `ExoPlayer` will call
this method when calculating the time to schedule its work task.
- Add `MediaCodecAdapter#OnBufferAvailableListener` to alert when input
and output buffers are available for use by `MediaCodecRenderer`.
`MediaCodecRenderer` will signal `ExoPlayer` when receiving these
callbacks and if `ExoPlayer` is set with
`experimentalSetDynamicSchedulingEnabled()`, then `ExoPlayer` will
schedule its work loop as renderers can make progress.
- Use data class for `LoadControl` methods instead of individual
parameters.
- Add `ExoPlayer.isReleased()` to check whether `Exoplayer.release()`
has
been called.
- Add `ExoPlayer.Builder.setMaxSeekToPreviousPositionMs()` to configure
the maximum position for which `seekToPrevious()` seeks to the previous
item ([#​1425](https://togithub.com/androidx/media/issues/1425)).
- Fix some audio focus inconsistencies, e.g. not reporting full or
transient focus loss while the player is paused
([#​1436](https://togithub.com/androidx/media/issues/1436)).
- Fix potential `IndexOutOfBoundsException` caused by extractors
reporting
additional tracks after the initial preparation step
([#​1476](https://togithub.com/androidx/media/issues/1476)).
- `Effects` in `ExoPlayer.setVideoEffect()` will receive the timestamps
with the renderer offset removed
([#​1098](https://togithub.com/androidx/media/issues/1098)).
- Fix potential `IllegalArgumentException` when handling player error
that
happened while reading ahead into another playlist item
([#​1483](https://togithub.com/androidx/media/issues/1483)).
- Transformer:
- Add `audioConversionProcess` and `videoConversionProcess` to
`ExportResult` indicating how the respective track in the output file
was made.
- Relax trim optimization H.264 level checks.
- Add support for changing between SDR and HDR input media in a
sequence.
- Add support for composition-level audio effects.
- Add support for transcoding Ultra HDR images into HDR videos.
- Fix issue where the `DefaultAudioMixer` does not output the correct
amount of bytes after being reset and reused.
- Work around a decoder bug where the number of audio channels was
capped
at stereo when handling PCM input.
- When selecting tracks in `ExoPlayerAssetLoader`, ignore audio channel
count constraints as they only apply for playback.
- Replace `androidx.media3.transformer.Muxer` interface with
`androidx.media3.muxer.Muxer` and remove
`androidx.media3.transformer.Muxer`.
- Fix HEIC image loading from content URI schemes.
([#​1373](https://togithub.com/androidx/media/issues/1373)).
- Adjust audio track duration in `AudioGraphInput` to improve AV sync.
- Remove `ExportResult.processedInputs` field. If you use this field for
codec details, then use `DefaultDecoderFactory.listener` instead. In
case of a codec exception, codec details will be available in the
`ExportException.codecInfo`.
- Extractors:
- MPEG-TS: Roll forward the change ensuring the last frame is rendered
by
passing the last access unit of a stream to the sample queue
([#​7909](https://togithub.com/google/ExoPlayer/issues/7909)).
Incorporating fixes to resolve the issues that emerged in I-frame only
HLS
streams([#​1150](https://togithub.com/androidx/media/issues/1150))
and
H.262 HLS streams
([#​1126](https://togithub.com/androidx/media/issues/1126)).
- MP3: Prefer the data size from an `Info` frame over the size reported
by
the underlying stream (e.g. file size, or HTTP `Content-Length` header).
This helps to exclude non-playable trailer data (e.g. album artwork)
from constant bitrate seeking calculations, making seeks more accurate
([#​1376](https://togithub.com/androidx/media/issues/1376)).
- MP3: Use the frame count and other data in an `Info` frame (if
present)
to compute an average bitrate for constant bitrate seeking, rather than
extrapolating from the bitrate of the frame after the `Info` frame,
which may be artificially small, e.g. `PCUT` frame
([#​1376](https://togithub.com/androidx/media/issues/1376)).
- Fix PCM audio format extraction in AVI containers.
- Audio:
- Fix DTS:X Profile 2 encoding attributes for passthrough playback
([#​1299](https://togithub.com/androidx/media/pull/1299)).
- For offloaded playback, reset the tracking field for stream completion
in `DefaultAudioSink` prior to calling `AudioTrack.stop()` so that
`AudioTrack.StreamEventCallback#onPresentationEnded` correctly
identifies when all pending data has been played.
- Fix bug in `SilenceSkippingAudioProcessor` where transitions between
different audio formats (for example stereo to mono) can cause the
processor to throw an exception
([#​1352](https://togithub.com/androidx/media/issues/1352)).
- Implement `MediaCodecAudioRenderer.getDurationToProgressUs()` so that
ExoPlayer will dynamically schedule its main work loop to when the
MediaCodecAudioRenderer can make progress.
- Video:
- Fix issue where `Listener.onRenderedFirstFrame()` arrives too early
when
switching surfaces mid-playback.
- Fix decoder fallback logic for Dolby Vision to use a compatible AV1
decoder if needed
([#​1389](https://togithub.com/androidx/media/pull/1389)).
- Fix codec exception that may be caused by enabling a video renderer
mid-playback.
- Text:
- Fix issue where subtitles starting before a seek position are skipped.
This issue was only introduced in Media3 1.4.0-alpha01.
- Change default subtitle parsing behavior so it happens during
extraction
instead of during rendering (see
[ExoPlayer's architecture
diagram](https://developer.android.com/media/media3/exoplayer/glossary#exoplayer)
for the difference between extraction and rendering).
- This change can be overridden by calling **both**
`MediaSource.Factory.experimentalParseSubtitlesDuringExtraction(false)`
and `TextRenderer.experimentalSetLegacyDecodingEnabled(true)`. See
the
[docs on
customization](https://developer.android.com/media/media3/exoplayer/customization)
for how to plumb these components into an `ExoPlayer` instance.
These methods (and all support for legacy subtitle decoding) will be
removed in a future release.
- Apps with custom `SubtitleDecoder` implementations need to update
them to implement `SubtitleParser` instead (and
`SubtitleParser.Factory` instead of `SubtitleDecoderFactory`).
- PGS: Fix run-length decoding to resolve `0` as a color index, instead
of
a literal color value
([#​1367](https://togithub.com/androidx/media/pull/1367)).
- CEA-708: Ignore `rowLock` value. The CEA-708-E S-2023 spec states that
`rowLock` and `columnLock` should both be assumed to be true, regardless
of the values present in the stream (`columnLock` support is not
implemented, so it's effectively assumed to always be false).
- This was originally included in the `1.3.0-alpha01` release notes,
but the change was accidentally reverted before the `1.3.0-rc01`
release. This is now fixed, so the change is present again.
- CEA-708: Avoid duplicate newlines being added by ExoPlayer's naive
handling of the 'set pen location' command
([#​1315](https://togithub.com/androidx/media/pull/1315)).
- Fix an `IllegalArgumentException` from `LegacySubtitleUtil` when a
WebVTT subtitle sample contains no cues, e.g. as part of a DASH stream
([#​1516](https://togithub.com/androidx/media/issues/1516)).
- Metadata:
- Fix mapping of MP4 to ID3 sort tags. Previously the 'album sort'
(`soal`), 'artist sort' (`soar`) and 'album artist sort' (`soaa`) MP4
tags were wrongly mapped to the `TSO2`, `TSOA` and `TSOP` ID3 tags
([#​1302](https://togithub.com/androidx/media/issues/1302)).
- Fix reading of MP4 (/iTunes) numeric `gnre` (genre) and `tmpo` (tempo)
tags when the value is more than one byte long.
- Propagate ID3 `TCON` frame to `MediaMetadata.genre`
([#​1305](https://togithub.com/androidx/media/issues/1305)).
- Image:
- Add support for non-square DASH thumbnail grids
([#​1300](https://togithub.com/androidx/media/pull/1300)).
- Add support for AVIF for API 34+.
- Allow `null` as parameter for `ExoPlayer.setImageOutput()` to clear a
previously set `ImageOutput`.
- DataSource:
- Implement support for `android.resource://package/id` raw resource
URIs
where `package` is different to the package of the current application.
This wasn't previously documented to work, but is a more efficient way
of accessing resources in another package than by name.
- Eagerly check `url` is non-null in the `DataSpec` constructors. This
parameter was already annotated to be non-null.
- Allow `ByteArrayDataSource` to resolve a URI to a byte array during
`open()`, instead of being hard-coded at construction
([#​1405](https://togithub.com/androidx/media/issues/1405)).
- DRM:
- Allow setting a `LoadErrorHandlingPolicy` on
`DefaultDrmSessionManagerProvider`
([#​1271](https://togithub.com/androidx/media/issues/1271)).
- Effect:
- Support multiple speed changes within the same `EditedMediaItem` or
`Composition` in `SpeedChangeEffect`.
- Support for HLG and PQ output from ultra HDR bitmap input.
- Add support for EGL_GL_COLORSPACE_BT2020\_HLG_EXT, which improves HLG
surface output in ExoPlayer.setVideoEffect and Transformer's Debug
SurfaceView.
- Update Overlay matrix implementation to make it consistent with the
documentation by flipping the x and y values applied in
`setOverlayFrameAnchor()`. If using
`OverlaySettings.Builder.setOverlayFrameAnchor()`, flip their x and y
values by multiplying them by `-1`.
- Fix bug where `TimestampWrapper` crashes when used with
`ExoPlayer#setVideoEffects`
([#​821](https://togithub.com/androidx/media/issues/821)).
- Change default SDR color working space from linear colors to
electrical
BT 709 SDR video. Also provide third option to retain the original
colorspace.
- Allow defining indeterminate z-order of EditedMediaItemSequences
([#​1055](https://togithub.com/androidx/media/pull/1055)).
- Maintain a consistent luminance range across different pieces of HDR
content (uses the HLG range).
- Add support for Ultra HDR (bitmap) overlays on HDR content.
- Allow `SeparableConvolution` effects to be used before API 26.
- Remove unused `OverlaySettings.useHdr` since dynamic range of overlay
and frame must match.
- Add HDR support for `TextOverlay`. Luminance of the text overlay can
be
adjusted with `OverlaySettings.Builder.setHdrLuminanceMultiplier()`.
- IMA extension:
- Promote API that is required for apps to play
[DAI ad
streams](https://developers.google.com/ad-manager/dynamic-ad-insertion/full-service)
to stable.
- Add `replaceAdTagParameters(Map <String, String>)` to
`ImaServerSideAdInsertionMediaSource.AdLoader` that allows replacing ad
tag parameters at runtime.
- Fix bug where `VideoAdPlayer.VideoAdPlayerCallback.onError()` was not
called when a player error happened during ad playback
([#​1334](https://togithub.com/androidx/media/issues/1334)).
- Bump IMA SDK version to 3.33.0 to fix a `NullPointerException` when
using `data://` ad tag URIs
([#​700](https://togithub.com/androidx/media/issues/700)).
- Session:
- Change default of `CommandButton.enabled` to `true` and ensure the
value
can stay false for controllers even if the associated command is
available.
- Add icon constants for `CommandButton` that should be used instead of
custom icon resources.
- Add `MediaSessionService.isPlaybackOngoing()` to let apps query
whether
the service needs to be stopped in `onTaskRemoved()`
([#​1219](https://togithub.com/androidx/media/issues/1219)).
- Add `MediaSessionService.pauseAllPlayersAndStopSelf()` that
conveniently
allows to pause playback of all sessions and call `stopSelf()` to
terminate the lifecycle of the `MediaSessionService`.
- Override `MediaSessionService.onTaskRemoved(Intent)` to provide a safe
default implementation that keeps the service running in the foreground
if playback is ongoing or stops the service otherwise.
- Hide seekbar in the media notification for live streams by not setting
the duration into the platform session metadata
([#​1256](https://togithub.com/androidx/media/issues/1256)).
- Align conversion of `MediaMetadata` to `MediaDescriptionCompat`, to
use
the same preferred order and logic when selecting metadata properties as
in media1.
- Add `MediaSession.sendError()` that allows sending non-fatal errors to
Media3 controller. When using the notification controller (see
`MediaSession.getMediaNotificationControllerInfo()`), the custom error
is used to update the `PlaybackState` of the platform session to an
error state with the given error information
([#​543](https://togithub.com/androidx/media/issues/543)).
- Add `MediaSession.Callback.onPlayerInteractionFinished()` to inform
sessions when a series of player interactions from a specific controller
finished.
- Add `SessionError` and use it in `SessionResult` and `LibraryResult`
instead of the error code to provide more information about the error
and how to resolve the error if possible.
- Publish the code for the media3 controller test app that can be used
to
test interactions with apps publishing a media session.
- Propagate extras passed to media3's
`MediaSession[Builder].setSessionExtras()` to a media1 controller's
`PlaybackStateCompat.getExtras()`.
- Map fatal and non-fatal errors to and from the platform session. A
`PlaybackException` is mapped to a fatal error state of the
`PlaybackStateCompat`. A `SessionError` sent to the media notification
controller with `MediaSession.sendError(ControllerInfo, SessionError)`
is mapped to a non-fatal error in `PlaybackStateCompat` which means that
error code and message are set but the state of the platform session
remains different to `STATE_ERROR`.
- Allow the session activity to be set per controller to override the
global session activity. The session activity can be defined for a
controller at connection time by creating a `ConnectionResult` with
`AcceptedResultBuilder.setSessionActivivty(PendingIntent)`. Once
connected, the session activity can be updated with
`MediaSession.setSessionActivity(ControllerInfo, PendingIntent)`.
- Improve error replication of calls to `MediaLibrarySession.Callback`.
Error replication can now be configured by using
`MediaLibrarySession.Builder.setLibraryErrorReplicationMode()` for
choosing the error type or opt-ing out of error replication which is on
by default.
- UI:
- Add image display support to `PlayerView` when connected to an
`ExoPlayer`
([#​1144](https://togithub.com/androidx/media/issues/1144)).
- Add customization of various icons in `PlayerControlView` through xml
attributes to allow different drawables per `PlayerView` instance,
rather than global overrides
([#​1200](https://togithub.com/androidx/media/issues/1200)).
- Work around a platform bug causing stretched/cropped video when using
`SurfaceView` inside a Compose `AndroidView` on API 34
([#​1237](https://togithub.com/androidx/media/issues/1237)).
- Downloads:
- Ensure that `DownloadHelper` does not leak unreleased `Renderer`
instances, which can eventually result in an app crashing with
`IllegalStateException: Too many receivers, total of 1000, registered
for pid`
([#​1224](https://togithub.com/androidx/media/issues/1224)).
- Cronet Extension:
- Fix `SocketTimeoutException` in `CronetDataSource`. In some versions
of
Cronet, the request provided by the callback is not always the same.
This leads to callback not completing and request timing out
(https://issuetracker.google.com/328442628).
- HLS Extension:
- Fix bug where pending EMSG samples waiting for a discontinuity were
delegated in `HlsSampleStreamWrapper` with an incorrect offset causing
an `IndexOutOfBoundsException` or an `IllegalArgumentException`
([#​1002](https://togithub.com/androidx/media/issues/1002)).
- Fix bug where non-primary playlists keep reloading for LL-HLS streams
([#​1240](https://togithub.com/androidx/media/issues/1240)).
- Fix bug where enabling CMCD for HLS with initialization segments
resulted in `Source Error` and `IllegalArgumentException`.
- Fix bug where non-primary playing playlists are not refreshed during
live playback
([#​1240](https://togithub.com/androidx/media/issues/1240)).
- Fix bug where enabling CMCD for HLS live streams causes
`ArrayIndexOutOfBoundsException`
([#​1395](https://togithub.com/androidx/media/issues/1395)).
- DASH Extension:
- Fix bug where re-preparing a multi-period live stream can throw an
`IndexOutOfBoundsException`
([#​1329](https://togithub.com/androidx/media/issues/1329)).
- Add support for `dashif:Laurl` license urls
([#​1345](https://togithub.com/androidx/media/issues/1345)).
- Cast Extension:
- Fix bug that converted the album title of the `MediaQueueItem` to the
artist in the Media3 media item
([#​1255](https://togithub.com/androidx/media/pull/1255)).
- Test Utilities:
- Implement `onInit()` and `onRelease()` in `FakeRenderer`.
- Change `TestPlayerRunHelper.runUntil()/playUntil()` methods to fail on
nonfatal errors (e.g. those reported to
`AnalyticsListener.onVideoCodecError()`). Use the new
`TestPlayerRunHelper.run(player).ignoringNonFatalErrors().untilXXX()`
method chain to disable this behavior.
- Demo app:
- Use `DefaultPreloadManager` in the short form demo app.
- Allow setting repeat mode with `Intent` arguments from command line
([#​1266](https://togithub.com/androidx/media/pull/1266)).
- Use `HttpEngineDataSource` as the `HttpDataSource` when supported by
the
device.
- Remove deprecated symbols:
- Remove `CronetDataSourceFactory`. Use `CronetDataSource.Factory`
instead.
- Remove some `DataSpec` constructors. Use `DataSpec.Builder` instead.
- Remove `setContentTypePredicate(Predicate)` method from
`DefaultHttpDataSource`, `OkHttpDataSource` and `CronetDataSource`. Use
the equivalent method on each `XXXDataSource.Factory` instead.
- Remove `OkHttpDataSource` constructors and `OkHttpDataSourceFactory`.
Use `OkHttpDataSource.Factory` instead.
- Remove `PlayerMessage.setHandler(Handler)`. Use `setLooper(Looper)`
instead.
- Remove `Timeline.Window.isLive` field. Use the `isLive()` method
instead.
- Remove `DefaultHttpDataSource` constructors. Use
`DefaultHttpDataSource.Factory` instead.
- Remove `DashMediaSource.DEFAULT_LIVE_PRESENTATION_DELAY_MS`. Use
`DashMediaSource.DEFAULT_FALLBACK_TARGET_LIVE_OFFSET_MS` instead.
- Remove `MediaCodecInfo.isSeamlessAdaptationSupported(Format, Format,
boolean)`. Use `MediaCodecInfo.canReuseCodec(Format, Format)` instead.
- Remove `DrmSessionManager.DUMMY` and `getDummyDrmSessionManager()`
method. Use `DrmSessionManager.DRM_UNSUPPORTED` instead.
- Remove `AnalyticsListener.onAudioInputFormatChanged(EventTime,
Format)`,
`AnalyticsListener.onVideoInputFormatChanged(EventTime, Format)`,
`AudioRendererEventListener.onAudioInputFormatChanged(Format)`,
`VideoRendererEventListener.onVideoInputFormatChanged(Format)`. Use the
overloads that take a `DecoderReuseEvaluation` instead.
- Remove `RendererSupport.FormatSupport` IntDef and `FORMAT_HANDLED`,
`FORMAT_EXCEEDS_CAPABILITIES`, `FORMAT_UNSUPPORTED_DRM`,
`FORMAT_UNSUPPORTED_SUBTYPE`, `FORMAT_UNSUPPORTED_TYPE` constants. Use
the equivalent IntDef and constants in `androidx.media3.common.C`
instead (e.g. `C.FORMAT_HANDLED`).
- Remove `Bundleable` interface. This includes removing all
`Bundleable.Creator<Foo> CREATOR` constant fields. Callers should use
the `Bundle toBundle()` and `static Foo fromBundle(Bundle)` methods on
each type instead.
</details>
---
### Configuration
📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).
🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.
♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box
---
This PR was generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View the
[repository job log](https://developer.mend.io/github/tuskyapp/Tusky).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy40MzguMCIsInVwZGF0ZWRJblZlciI6IjM3LjQzOC4wIiwidGFyZ2V0QnJhbmNoIjoiZGV2ZWxvcCIsImxhYmVscyI6W119-->
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-02 17:08:54 +02:00
|
|
|
androidx-media3 = "1.4.0"
|
2024-08-10 09:23:40 +02:00
|
|
|
androidx-paging = "3.3.2"
|
2023-11-01 11:39:08 +01:00
|
|
|
androidx-preference = "1.2.1"
|
2024-05-15 18:45:13 +02:00
|
|
|
androidx-recyclerview = "1.3.2"
|
2022-11-04 20:10:26 +01:00
|
|
|
androidx-sharetarget = "1.2.0"
|
2024-05-12 11:31:47 +02:00
|
|
|
androidx-splashscreen = "1.2.0-alpha01"
|
2022-11-04 20:10:26 +01:00
|
|
|
androidx-swiperefresh-layout = "1.1.0"
|
2023-02-28 21:35:31 +01:00
|
|
|
androidx-testing = "2.2.0"
|
2024-05-15 18:45:23 +02:00
|
|
|
androidx-viewpager2 = "1.1.0"
|
2024-08-10 09:24:11 +02:00
|
|
|
androidx-work = "2.9.1"
|
2024-02-23 12:21:31 +01:00
|
|
|
androidx-room = "2.6.1"
|
2022-11-04 20:10:26 +01:00
|
|
|
bouncycastle = "1.70"
|
2024-08-19 20:54:37 +02:00
|
|
|
conscrypt = "2.5.3"
|
2024-05-10 16:23:43 +02:00
|
|
|
coroutines = "1.8.1"
|
2023-03-10 20:30:55 +01:00
|
|
|
diffx = "1.1.1"
|
2024-02-23 11:28:14 +01:00
|
|
|
emoji2 = "1.4.0"
|
2024-07-03 19:53:16 +02:00
|
|
|
espresso = "3.6.1"
|
2023-01-09 21:06:47 +01:00
|
|
|
filemoji-compat = "3.2.7"
|
2023-09-11 19:29:50 +02:00
|
|
|
glide = "4.16.0"
|
2023-05-19 13:30:57 +02:00
|
|
|
# Deliberate downgrade, https://github.com/tuskyapp/Tusky/issues/3631
|
|
|
|
glide-animation-plugin = "2.23.0"
|
2024-08-06 20:10:12 +02:00
|
|
|
hilt = "2.52"
|
2024-08-23 09:15:46 +02:00
|
|
|
kotlin = "2.0.20"
|
2023-02-21 19:43:51 +01:00
|
|
|
image-cropper = "4.3.2"
|
2024-05-03 13:25:26 +02:00
|
|
|
material = "1.12.0"
|
2022-11-04 20:10:26 +01:00
|
|
|
material-drawer = "8.4.5"
|
2024-05-31 13:44:13 +02:00
|
|
|
material-typeface = "4.0.0.3-kotlin"
|
2023-03-13 10:20:08 +01:00
|
|
|
mockito-inline = "5.2.0"
|
2024-07-12 09:40:13 +02:00
|
|
|
mockito-kotlin = "5.4.0"
|
Replace Gson library with Moshi (#4309)
**! ! Warning**: Do not merge before testing every API call and database
read involving JSON !
**Gson** is obsolete and has been superseded by **Moshi**. But more
importantly, parsing Kotlin objects using Gson is _dangerous_ because
Gson uses Java serialization and is **not Kotlin-aware**. This has two
main consequences:
- Fields of non-null types may end up null at runtime. Parsing will
succeed, but the code may crash later with a `NullPointerException` when
trying to access a field member;
- Default values of constructor parameters are always ignored. When
absent, reference types will be null, booleans will be false and
integers will be zero.
On the other hand, Kotlin-aware parsers like **Moshi** or **Kotlin
Serialization** will validate at parsing time that all received fields
comply with the Kotlin contract and avoid errors at runtime, making apps
more stable and schema mismatches easier to detect (as long as logs are
accessible):
- Receiving a null value for a non-null type will generate a parsing
error;
- Optional types are declared explicitly by adding a default value. **A
missing value with no default value declaration will generate a parsing
error.**
Migrating the entity declarations from Gson to Moshi will make the code
more robust but is not an easy task because of the semantic differences.
With Gson, both nullable and optional fields are represented with a null
value. After converting to Moshi, some nullable entities can become
non-null with a default value (if they are optional and not nullable),
others can stay nullable with no default value (if they are mandatory
and nullable), and others can become **nullable with a default value of
null** (if they are optional _or_ nullable _or_ both). That third option
is the safest bet when it's not clear if a field is optional or not,
except for lists which can usually be declared as non-null with a
default value of an empty list (I have yet to see a nullable array type
in the Mastodon API).
Fields that are currently declared as non-null present another
challenge. In theory, they should remain as-is and everything will work
fine. In practice, **because Gson is not aware of nullable types at
all**, it's possible that some non-null fields currently hold a null
value in some cases but the app does not report any error because the
field is not accessed by Kotlin code in that scenario. After migrating
to Moshi however, parsing such a field will now fail early if a null
value or no value is received.
These fields will have to be identified by heavily testing the app and
looking for parsing errors (`JsonDataException`) and/or by going through
the Mastodon documentation. A default value needs to be added for
missing optional fields, and their type could optionally be changed to
nullable, depending on the case.
Gson is also currently used to serialize and deserialize objects to and
from the local database, which is also challenging because backwards
compatibility needs to be preserved. Fortunately, by default Gson omits
writing null fields, so a field of type `List<T>?` could be replaced
with a field of type `List<T>` with a default value of `emptyList()` and
reading back the old data should still work. However, nullable lists
that are written directly (not as a field of another object) will still
be serialized to JSON as `"null"` so the deserializing code must still
be handling null properly.
Finally, changing the database schema is out of scope for this pull
request, so database entities that also happen to be serialized with
Gson will keep their original types even if they could be made non-null
as an improvement.
In the end this is all for the best, because the app will be more
reliable and errors will be easier to detect by showing up earlier with
a clear error message. Not to mention the performance benefits of using
Moshi compared to Gson.
- Replace Gson reflection with Moshi Kotlin codegen to generate all
parsers at compile time.
- Replace custom `Rfc3339DateJsonAdapter` with the one provided by
moshi-adapters.
- Replace custom `JsonDeserializer` classes for Enum types with
`EnumJsonAdapter.create(T).withUnknownFallback()` from moshi-adapters to
support fallback values.
- Replace `GuardedBooleanAdapter` with the more generic `GuardedAdapter`
which works with any type. Any nullable field may now be annotated with
`@Guarded`.
- Remove Proguard rules related to Json entities. Each Json entity needs
to be annotated with `@JsonClass` with no exception, and adding this
annotation will ensure that R8/Proguard will handle the entities
properly.
- Replace some nullable Boolean fields with non-null Boolean fields with
a default value where possible.
- Replace some nullable list fields with non-null list fields with a
default value of `emptyList()` where possible.
- Update `TimelineDao` to perform all Json conversions internally using
`Converters` so no Gson or Moshi instance has to be passed to its
methods.
- ~~Create a custom `DraftAttachmentJsonAdapter` to serialize and
deserialize `DraftAttachment` which is a special entity that supports
more than one json name per field. A custom adapter is necessary because
there is not direct equivalent of `@SerializedName(alternate = [...])`
in Moshi.~~ Remove alternate names for some `DraftAttachment` fields
which were used as a workaround to deserialize local data in 2-years old
builds of Tusky.
- Update tests to make them work with Moshi.
- Simplify a few `equals()` implementations.
- Change a few functions to `val`s
- Turn `NetworkModule` into an `object` (since it contains no abstract
methods).
Please test the app thoroughly before merging. There may be some fields
currently declared as mandatory that are actually optional.
2024-04-02 21:01:04 +02:00
|
|
|
moshi = "1.15.1"
|
2024-07-15 21:48:12 +02:00
|
|
|
networkresult-calladapter = "1.2.0"
|
2024-02-23 20:02:17 +01:00
|
|
|
okhttp = "4.12.0"
|
2024-04-14 16:39:29 +02:00
|
|
|
okio = "3.9.0"
|
2024-03-29 10:16:03 +01:00
|
|
|
retrofit = "2.11.0"
|
2024-07-12 09:40:03 +02:00
|
|
|
robolectric = "4.13"
|
2023-09-10 10:05:11 +02:00
|
|
|
sparkbutton = "4.2.0"
|
2024-02-23 10:29:37 +01:00
|
|
|
touchimageview = "3.6"
|
2024-07-14 09:02:19 +02:00
|
|
|
truth = "1.4.4"
|
2024-03-09 13:32:30 +01:00
|
|
|
turbine = "1.1.0"
|
2024-02-23 10:53:31 +01:00
|
|
|
unified-push = "2.4.0"
|
2023-03-10 20:30:55 +01:00
|
|
|
xmlwriter = "1.0.4"
|
2022-11-04 20:10:26 +01:00
|
|
|
|
2023-02-04 19:58:53 +01:00
|
|
|
[plugins]
|
|
|
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
2024-08-23 09:15:46 +02:00
|
|
|
google-ksp = "com.google.devtools.ksp:2.0.20-1.0.24"
|
2024-05-10 15:55:07 +02:00
|
|
|
hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
|
2023-02-04 19:58:53 +01:00
|
|
|
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
|
|
|
kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
|
2024-05-10 15:55:54 +02:00
|
|
|
ktlint = "org.jlleitschuh.gradle.ktlint:12.1.1"
|
2023-02-04 19:58:53 +01:00
|
|
|
|
2022-11-04 20:10:26 +01:00
|
|
|
[libraries]
|
|
|
|
android-material = { module = "com.google.android.material:material", version.ref = "material" }
|
2024-05-03 13:21:37 +02:00
|
|
|
androidx-activity = { module = "androidx.activity:activity", version.ref = "androidx-activity" }
|
2022-11-04 20:10:26 +01:00
|
|
|
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
|
|
|
|
androidx-browser = { module = "androidx.browser:browser", version.ref = "androidx-browser" }
|
|
|
|
androidx-cardview = { module = "androidx.cardview:cardview", version.ref = "androidx-cardview" }
|
|
|
|
androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidx-constraintlayout" }
|
|
|
|
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
|
|
|
|
androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidx-splashscreen" }
|
|
|
|
androidx-core-testing = { module = "androidx.arch.core:core-testing", version.ref = "androidx-testing" }
|
2024-05-10 17:14:26 +02:00
|
|
|
androidx-drawerlayout = { group = "androidx.drawerlayout", name = "drawerlayout", version.ref = "androidx-drawerlayout" }
|
2022-11-04 20:10:26 +01:00
|
|
|
androidx-emoji2-core = { module = "androidx.emoji2:emoji2", version.ref = "emoji2" }
|
|
|
|
androidx-emoji2-views-core = { module = "androidx.emoji2:emoji2-views", version.ref = "emoji2" }
|
|
|
|
androidx-emoji2-view-helper = { module = "androidx.emoji2:emoji2-views-helper", version.ref = "emoji2" }
|
|
|
|
androidx-exifinterface = { module = "androidx.exifinterface:exifinterface", version.ref = "androidx-exifinterface" }
|
|
|
|
androidx-fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "androidx-fragment" }
|
2024-05-10 15:55:07 +02:00
|
|
|
androidx-hilt-compiler = { module = "androidx.hilt:hilt-compiler", version.ref = "androidx-hilt" }
|
|
|
|
androidx-hilt-work = { module = "androidx.hilt:hilt-work", version.ref = "androidx-hilt" }
|
2023-03-13 13:16:49 +01:00
|
|
|
androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle" }
|
2023-08-10 19:31:55 +02:00
|
|
|
androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "androidx-media3" }
|
|
|
|
androidx-media3-datasource-okhttp = { module = "androidx.media3:media3-datasource-okhttp", version.ref = "androidx-media3" }
|
|
|
|
androidx-media3-ui = { module = "androidx.media3:media3-ui", version.ref = "androidx-media3" }
|
2022-11-04 20:10:26 +01:00
|
|
|
androidx-paging-runtime-ktx = { module = "androidx.paging:paging-runtime-ktx", version.ref = "androidx-paging" }
|
|
|
|
androidx-preference-ktx = { module = "androidx.preference:preference-ktx", version.ref = "androidx-preference" }
|
2023-02-04 20:22:29 +01:00
|
|
|
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "androidx-room" }
|
|
|
|
androidx-room-paging = { module = "androidx.room:room-paging", version.ref = "androidx-room" }
|
|
|
|
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "androidx-room" }
|
|
|
|
androidx-room-testing = { module = "androidx.room:room-testing", version.ref = "androidx-room" }
|
2022-11-04 20:10:26 +01:00
|
|
|
androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "androidx-recyclerview" }
|
|
|
|
androidx-sharetarget = { module = "androidx.sharetarget:sharetarget", version.ref = "androidx-sharetarget" }
|
|
|
|
androidx-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "androidx-swiperefresh-layout" }
|
|
|
|
androidx-test-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-junit" }
|
|
|
|
androidx-viewpager2 = { module = "androidx.viewpager2:viewpager2", version.ref = "androidx-viewpager2" }
|
2023-06-11 13:17:30 +02:00
|
|
|
androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "androidx-work" }
|
2022-11-07 20:04:07 +01:00
|
|
|
androidx-work-testing = { module = "androidx.work:work-testing", version.ref = "androidx-work" }
|
2022-11-04 20:10:26 +01:00
|
|
|
bouncycastle = { module = "org.bouncycastle:bcprov-jdk15on", version.ref = "bouncycastle" }
|
|
|
|
conscrypt-android = { module = "org.conscrypt:conscrypt-android", version.ref = "conscrypt" }
|
2024-05-10 15:55:07 +02:00
|
|
|
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
|
|
|
|
hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" }
|
2023-03-10 20:30:55 +01:00
|
|
|
diffx = { module = "org.pageseeder.diffx:pso-diffx", version.ref = "diffx" }
|
2022-11-04 20:10:26 +01:00
|
|
|
espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" }
|
|
|
|
filemojicompat-core = { module = "de.c1710:filemojicompat", version.ref = "filemoji-compat" }
|
|
|
|
filemojicompat-defaults = { module = "de.c1710:filemojicompat-defaults", version.ref = "filemoji-compat" }
|
|
|
|
filemojicompat-ui = { module = "de.c1710:filemojicompat-ui", version.ref = "filemoji-compat" }
|
|
|
|
glide-animation-plugin = { module = "com.github.penfeizhou.android.animation:glide-plugin", version.ref = "glide-animation-plugin" }
|
2023-07-11 15:34:14 +02:00
|
|
|
glide-compiler = { module = "com.github.bumptech.glide:ksp", version.ref = "glide" }
|
2022-11-04 20:10:26 +01:00
|
|
|
glide-core = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
|
|
|
|
glide-okhttp3-integration = { module = "com.github.bumptech.glide:okhttp3-integration", version.ref = "glide" }
|
|
|
|
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
|
|
|
|
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
|
|
|
|
image-cropper = { module = "com.github.CanHub:Android-Image-Cropper", version.ref = "image-cropper" }
|
|
|
|
material-drawer-core = { module = "com.mikepenz:materialdrawer", version.ref = "material-drawer" }
|
|
|
|
material-drawer-iconics = { module = "com.mikepenz:materialdrawer-iconics", version.ref = "material-drawer" }
|
|
|
|
material-typeface = { module = "com.mikepenz:google-material-typeface", version.ref = "material-typeface" }
|
|
|
|
mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = "mockito-kotlin" }
|
|
|
|
mockito-inline = { module = "org.mockito:mockito-inline", version.ref = "mockito-inline" }
|
|
|
|
mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp" }
|
Replace Gson library with Moshi (#4309)
**! ! Warning**: Do not merge before testing every API call and database
read involving JSON !
**Gson** is obsolete and has been superseded by **Moshi**. But more
importantly, parsing Kotlin objects using Gson is _dangerous_ because
Gson uses Java serialization and is **not Kotlin-aware**. This has two
main consequences:
- Fields of non-null types may end up null at runtime. Parsing will
succeed, but the code may crash later with a `NullPointerException` when
trying to access a field member;
- Default values of constructor parameters are always ignored. When
absent, reference types will be null, booleans will be false and
integers will be zero.
On the other hand, Kotlin-aware parsers like **Moshi** or **Kotlin
Serialization** will validate at parsing time that all received fields
comply with the Kotlin contract and avoid errors at runtime, making apps
more stable and schema mismatches easier to detect (as long as logs are
accessible):
- Receiving a null value for a non-null type will generate a parsing
error;
- Optional types are declared explicitly by adding a default value. **A
missing value with no default value declaration will generate a parsing
error.**
Migrating the entity declarations from Gson to Moshi will make the code
more robust but is not an easy task because of the semantic differences.
With Gson, both nullable and optional fields are represented with a null
value. After converting to Moshi, some nullable entities can become
non-null with a default value (if they are optional and not nullable),
others can stay nullable with no default value (if they are mandatory
and nullable), and others can become **nullable with a default value of
null** (if they are optional _or_ nullable _or_ both). That third option
is the safest bet when it's not clear if a field is optional or not,
except for lists which can usually be declared as non-null with a
default value of an empty list (I have yet to see a nullable array type
in the Mastodon API).
Fields that are currently declared as non-null present another
challenge. In theory, they should remain as-is and everything will work
fine. In practice, **because Gson is not aware of nullable types at
all**, it's possible that some non-null fields currently hold a null
value in some cases but the app does not report any error because the
field is not accessed by Kotlin code in that scenario. After migrating
to Moshi however, parsing such a field will now fail early if a null
value or no value is received.
These fields will have to be identified by heavily testing the app and
looking for parsing errors (`JsonDataException`) and/or by going through
the Mastodon documentation. A default value needs to be added for
missing optional fields, and their type could optionally be changed to
nullable, depending on the case.
Gson is also currently used to serialize and deserialize objects to and
from the local database, which is also challenging because backwards
compatibility needs to be preserved. Fortunately, by default Gson omits
writing null fields, so a field of type `List<T>?` could be replaced
with a field of type `List<T>` with a default value of `emptyList()` and
reading back the old data should still work. However, nullable lists
that are written directly (not as a field of another object) will still
be serialized to JSON as `"null"` so the deserializing code must still
be handling null properly.
Finally, changing the database schema is out of scope for this pull
request, so database entities that also happen to be serialized with
Gson will keep their original types even if they could be made non-null
as an improvement.
In the end this is all for the best, because the app will be more
reliable and errors will be easier to detect by showing up earlier with
a clear error message. Not to mention the performance benefits of using
Moshi compared to Gson.
- Replace Gson reflection with Moshi Kotlin codegen to generate all
parsers at compile time.
- Replace custom `Rfc3339DateJsonAdapter` with the one provided by
moshi-adapters.
- Replace custom `JsonDeserializer` classes for Enum types with
`EnumJsonAdapter.create(T).withUnknownFallback()` from moshi-adapters to
support fallback values.
- Replace `GuardedBooleanAdapter` with the more generic `GuardedAdapter`
which works with any type. Any nullable field may now be annotated with
`@Guarded`.
- Remove Proguard rules related to Json entities. Each Json entity needs
to be annotated with `@JsonClass` with no exception, and adding this
annotation will ensure that R8/Proguard will handle the entities
properly.
- Replace some nullable Boolean fields with non-null Boolean fields with
a default value where possible.
- Replace some nullable list fields with non-null list fields with a
default value of `emptyList()` where possible.
- Update `TimelineDao` to perform all Json conversions internally using
`Converters` so no Gson or Moshi instance has to be passed to its
methods.
- ~~Create a custom `DraftAttachmentJsonAdapter` to serialize and
deserialize `DraftAttachment` which is a special entity that supports
more than one json name per field. A custom adapter is necessary because
there is not direct equivalent of `@SerializedName(alternate = [...])`
in Moshi.~~ Remove alternate names for some `DraftAttachment` fields
which were used as a workaround to deserialize local data in 2-years old
builds of Tusky.
- Update tests to make them work with Moshi.
- Simplify a few `equals()` implementations.
- Change a few functions to `val`s
- Turn `NetworkModule` into an `object` (since it contains no abstract
methods).
Please test the app thoroughly before merging. There may be some fields
currently declared as mandatory that are actually optional.
2024-04-02 21:01:04 +02:00
|
|
|
moshi-core = { module = "com.squareup.moshi:moshi", version.ref = "moshi" }
|
|
|
|
moshi-adapters = { module = "com.squareup.moshi:moshi-adapters", version.ref = "moshi" }
|
|
|
|
moshi-kotlin-codegen = { module = "com.squareup.moshi:moshi-kotlin-codegen", version.ref = "moshi" }
|
2022-11-04 20:10:26 +01:00
|
|
|
networkresult-calladapter = { module = "at.connyduck:networkresult-calladapter", version.ref = "networkresult-calladapter" }
|
|
|
|
okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
|
|
|
|
okhttp-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
|
2024-04-14 16:39:29 +02:00
|
|
|
okio = { module = "com.squareup.okio:okio", version.ref = "okio" }
|
Replace Gson library with Moshi (#4309)
**! ! Warning**: Do not merge before testing every API call and database
read involving JSON !
**Gson** is obsolete and has been superseded by **Moshi**. But more
importantly, parsing Kotlin objects using Gson is _dangerous_ because
Gson uses Java serialization and is **not Kotlin-aware**. This has two
main consequences:
- Fields of non-null types may end up null at runtime. Parsing will
succeed, but the code may crash later with a `NullPointerException` when
trying to access a field member;
- Default values of constructor parameters are always ignored. When
absent, reference types will be null, booleans will be false and
integers will be zero.
On the other hand, Kotlin-aware parsers like **Moshi** or **Kotlin
Serialization** will validate at parsing time that all received fields
comply with the Kotlin contract and avoid errors at runtime, making apps
more stable and schema mismatches easier to detect (as long as logs are
accessible):
- Receiving a null value for a non-null type will generate a parsing
error;
- Optional types are declared explicitly by adding a default value. **A
missing value with no default value declaration will generate a parsing
error.**
Migrating the entity declarations from Gson to Moshi will make the code
more robust but is not an easy task because of the semantic differences.
With Gson, both nullable and optional fields are represented with a null
value. After converting to Moshi, some nullable entities can become
non-null with a default value (if they are optional and not nullable),
others can stay nullable with no default value (if they are mandatory
and nullable), and others can become **nullable with a default value of
null** (if they are optional _or_ nullable _or_ both). That third option
is the safest bet when it's not clear if a field is optional or not,
except for lists which can usually be declared as non-null with a
default value of an empty list (I have yet to see a nullable array type
in the Mastodon API).
Fields that are currently declared as non-null present another
challenge. In theory, they should remain as-is and everything will work
fine. In practice, **because Gson is not aware of nullable types at
all**, it's possible that some non-null fields currently hold a null
value in some cases but the app does not report any error because the
field is not accessed by Kotlin code in that scenario. After migrating
to Moshi however, parsing such a field will now fail early if a null
value or no value is received.
These fields will have to be identified by heavily testing the app and
looking for parsing errors (`JsonDataException`) and/or by going through
the Mastodon documentation. A default value needs to be added for
missing optional fields, and their type could optionally be changed to
nullable, depending on the case.
Gson is also currently used to serialize and deserialize objects to and
from the local database, which is also challenging because backwards
compatibility needs to be preserved. Fortunately, by default Gson omits
writing null fields, so a field of type `List<T>?` could be replaced
with a field of type `List<T>` with a default value of `emptyList()` and
reading back the old data should still work. However, nullable lists
that are written directly (not as a field of another object) will still
be serialized to JSON as `"null"` so the deserializing code must still
be handling null properly.
Finally, changing the database schema is out of scope for this pull
request, so database entities that also happen to be serialized with
Gson will keep their original types even if they could be made non-null
as an improvement.
In the end this is all for the best, because the app will be more
reliable and errors will be easier to detect by showing up earlier with
a clear error message. Not to mention the performance benefits of using
Moshi compared to Gson.
- Replace Gson reflection with Moshi Kotlin codegen to generate all
parsers at compile time.
- Replace custom `Rfc3339DateJsonAdapter` with the one provided by
moshi-adapters.
- Replace custom `JsonDeserializer` classes for Enum types with
`EnumJsonAdapter.create(T).withUnknownFallback()` from moshi-adapters to
support fallback values.
- Replace `GuardedBooleanAdapter` with the more generic `GuardedAdapter`
which works with any type. Any nullable field may now be annotated with
`@Guarded`.
- Remove Proguard rules related to Json entities. Each Json entity needs
to be annotated with `@JsonClass` with no exception, and adding this
annotation will ensure that R8/Proguard will handle the entities
properly.
- Replace some nullable Boolean fields with non-null Boolean fields with
a default value where possible.
- Replace some nullable list fields with non-null list fields with a
default value of `emptyList()` where possible.
- Update `TimelineDao` to perform all Json conversions internally using
`Converters` so no Gson or Moshi instance has to be passed to its
methods.
- ~~Create a custom `DraftAttachmentJsonAdapter` to serialize and
deserialize `DraftAttachment` which is a special entity that supports
more than one json name per field. A custom adapter is necessary because
there is not direct equivalent of `@SerializedName(alternate = [...])`
in Moshi.~~ Remove alternate names for some `DraftAttachment` fields
which were used as a workaround to deserialize local data in 2-years old
builds of Tusky.
- Update tests to make them work with Moshi.
- Simplify a few `equals()` implementations.
- Change a few functions to `val`s
- Turn `NetworkModule` into an `object` (since it contains no abstract
methods).
Please test the app thoroughly before merging. There may be some fields
currently declared as mandatory that are actually optional.
2024-04-02 21:01:04 +02:00
|
|
|
retrofit-converter-moshi = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "retrofit" }
|
2022-11-04 20:10:26 +01:00
|
|
|
retrofit-core = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
|
|
|
|
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
|
2023-09-10 10:05:11 +02:00
|
|
|
sparkbutton = { module = "at.connyduck.sparkbutton:sparkbutton", version.ref = "sparkbutton" }
|
Fix image zoom / pan / scroll / swipe (#3894)
Migrate to touchimageview from photoview, and adjust the touch logic to correctly handle single finger drag, two finger pinch/stretch, flings, taps, and swipes.
As before, the features are:
- Single tap, show/hide controls and media description
- Double tap, zoom in/out
- Single finger drag up/down, scale/translate image, dismiss if scrolled too far
- Single finger drag left/right
- When not zoomed, swipe to next image if multiple images present
- When zoomed, scroll to edge of image, then to next image if multiple images present
- Two finger pinch/zoom, zoom in/out on the image
Behaviour differences to previous code
1. Bug fix: The image can't get "stuck" when zoomed, and impossible to scroll
2. Bug fix: Pinching is not mis-interpreted as a fling, closing the image
3. Bug fix: The zoom state of images is not lost or misinterpreted when the user swipes through multiple images
4. Bug fix: Double-tap zooms all the way, instead of stopping
5. Tapping outside the image does not dismiss it, controls and description show/hide
Fixes https://github.com/tuskyapp/Tusky/issues/3562, https://github.com/tuskyapp/Tusky/issues/2297
2023-07-31 12:44:01 +02:00
|
|
|
touchimageview = { module = "com.github.MikeOrtiz:TouchImageView", version.ref = "touchimageview" }
|
Convert NotificationsFragment and related code to Kotlin, use the Paging library (#3159)
* Unmodified output from "Convert Java to Kotlin" on NotificationsFragment.java
* Bare minimum changes to get this to compile and run
- Use `lateinit` for `eventhub`, `adapter`, `preferences`, and `scrolllistener`
- Removed override for accountManager, it can be used from the superclass
- Add `?.` where non-nullity could not (yet) be guaranteed
- Remove `?` from type lists where non-nullity is guaranteed
- Explicitly convert lists to mutable where necessary
- Delete unused function `findReplyPosition`
* Remove all unnecessary non-null (!!) assertions
The previous change meant some values are no longer nullable. Remove the
non-null assertions.
* Lint ListStatusAccessibilityDelegate call
- Remove redundant constructor
- Move block outside of `()`
* Use `let` when handling compose button visibility on scroll
* Replace a `requireNonNull` with `!!`
* Remove redundant return values
* Remove or rename unused lambda parameters
* Remove unnecessary type parameters
* Remove unnecessary null checks
* Replace cascading-if statement with `when`
* Simplify calculation of `topId`
* Use more appropriate list properties and methods
- Access the last value with `.last()`
- Access the last index with `.lastIndex`
- Replace logical-chain with `asRightOrNull` and `?.`
- `.isNotEmpty()`, not `!...isEmpty()`
* Inline unnecessary variable
* Use PrefKeys constants instead of bare strings
* Use `requireContext()` instead of `context!!`
* Replace deprecated `onActivityCreated()` with `onViewCreated()`
* Remove unnecessary variable setting
* Replace `size == 0` check with `isEmpty()`
* Format with ktlint, no functionality changes
* Convert NotifcationsAdapter to Kotlin
Does not compile, this is the unchanged output of the "Convert to Kotlin"
function
* Minimum changes to get NotificationsAdapter to compile
* Remove unnecessary visibility modifiers
* Use `isNotEmpty()`
* Remove unused lambda parameters
* Convert cascading-if to `when`
* Simplifiy assignment op
* Use explicit argument names with `copy()`
* Use `.firstOrNull()` instead of `if`
* Mark as lateinit to avoid unnecessary null checks
* Format with ktlint, whitespace changes only
* Bare minimum necessary to demonstrate paging in notifications
Create `NotificationsPagingSource`. This uses a new `notifications2()` API
call, which will exist until all the code has been adapted. Instead of
using placeholders,
Create `NotificationsPagingAdapter` (will replace `NotificationsAdapater`)
to consume this data.
Expose the paging source view a new `NotificationsViewModel` `flow`, and
submit new pages to the adapter as they are available in
`NotificationsFragment`.
Comment out any other code in `NotificationsFragment` that deals with
loading data from the network. This will be updated as necessary, either
here, or in the view model.
Lots of functionality is missing, including:
- Different views for different notification types
- Starting at the remembered notification position
- Interacting with notifications
- Adjusting the UI state to match the loading state
These will be added incrementally.
* Migrate StatusNotificationViewHolder impl. to NotificationsPagingAdapter
With this change `NotificationsPagingAdapter` shows notifications about a
status correctly.
- Introduce a `ViewHolder` abstract class that all Notification view holders
derive from. Modify the fallback view holder to use this.
- Implement `StatusNotificationViewHolder`. Much of the code is from the
existing implementation in the `NotificationAdapater`.
- The original code split the code that binds values to views between the
adapter's `bindViewHolder` method and the view holder's methods.
In this code, all of the binding code is in the view holder, in a `bind`
method. This is called by the adapter's `bindViewHolder` method. This keeps
all the binding logic in the view holder, where it belongs.
- The new `StatusNotificationViewHolder` uses view binding to access its views
instead of `findViewById`.
- Logically, information about whether to show sensitive media, or open
content warnings should be part of the `StatusDisplayOptions`. So add those
as fields, and populate them appropriately.
This affects code outside notification handling, which will be adjusted
later.
* Note some TODOs to complete before the PR is finished
* Extract StatusNotificationViewHolder to a new file
* Add TODO for NotificationViewData.Concrete
* Convert the adapter to take NotificationViewData.Concrete
* Add a view holder for regular status notifications
* Migrate Follow and FollowRequest notifications
* Migrate report notifications
* Convert onViewThread to use the adapter data
* Convert onViewMedia to use the adapter data
* Convert onMore to use the adapter data
* Convert onReply to use the adapter data
* Convert NotificationViewData to Kotlin
* Re-implement the reblog functionality
- Move reblogging in to the view model
- Update the UI via the adapter's `snapshot()` and `notifyItemChanged()`
methods
* Re-implement the favourite functionality
Same approach as reblog
* Re-implement the bookmark functionality
Same approach as reblog
* Add TODO re StatusActionListener interface
* Add TODO re event handling
* Re-implementing the voting functionality
* Re-implement viewing hidden content
- Hidden media
- Content behind a content warning
* Add a TODO re pinning
* Re-implement "Show more" / "Show less"
* Delete unused updateStatus() function
* Comment out the scroll listener for the moment
* Re-implement applying filters to notifications
Introduce `NotificationsRepository`, to provide access to the notifications
stream.
When changing the filters the flow is as follows:
- User clicks "Apply" in the fragment.
- Fragment calls `viewModel.accept()` with a `UiAction.ApplyFilter` (new
class).
- View model maintains a private flow of incoming UI actions. The new action
is emitted to that flow.
- In view model, `notificationFilter` waits for `.ApplyFilter` actions, and
ensures the filter is saved, then emits it.
- In view model, `pagingDataFlow` waits for new items from
`notificationsFilter` and fetches the notifications from the repository in
response. The repository provides `Notification`, so the model maps them to
`NotificationViewData.Concrete` for display by the adapter.
- In view model the UI state also waits for new items from
`notificationsFilter` and emits a new `UiState` every time the filter is
changed.
When opening the fragment for the first time:
- All of the above machinery, but `notificationFilter` also fetches the filter
from the active account and emits that first. This triggers the first fetch
and the first update of `uiState`.
Also:
- Add TODOs for functionality that is not implemented yet
- Delete a lot of dead code from NotificationsFragment
* Include important preference values in `uiState`
Listen to the flow of eventHub events, filtered to preference changes that
are relevant to the notification view.
When preferences change (or when the view model starts), fetch the current
values, and include them in `uiState`.
Remove preference handling from `NotificationsFragment`, and just use
the values from `uiState`.
Adjust how the `useAbsoluteTime` preference is handled. The previous code
loaded new content (via a diffutil) in to the adapter, which would trigger
a re-binding of the timestamp.
As the adapter content is immutable, the new code simply triggers a
re-binding of the views that are currently visible on screen.
* Update UI in response to different load states
Notifications can be loaded at the top and bottom of the timeline. Add a
new layout to show the progress of these loads, and any errors that can
occur.
Catch network errors in `NotificationsPagingSource` and convert to
`LoadState.Error`.
Add a header/footer to the notifications list to show the load state.
Collect the load state from the adapter, use this to drive the visibility
of different views.
* Save and restore the last read notification ID
Use this when fetching notifications, to centre the list around the
notification that was last read.
* Call notifyItemRangeChanged with the correct parameters
* Don't try and save list position if there are no items in the list
* Show/hide the "Nothing to see" view appropriately
* Update comments
* Handle the case where the notification key no longer exists
* Re-implement support for showMediaPreview and other settings
* Re-implement "hide FAB when scrolling" preference
* Delete dead code
* Delete Notifications Adapater and Placeholder types
* Remove NotificationViewData.Concrete subclass
Now there's no Placeholder, everything is a NotificationViewData.
* Improve how notification pages are loaded if the first notification is missing or filtered
* Re-implement clear notifications, show errors
* s/default/from/
* Add missing headers
* Don't process bookmarking via EventHub
- Initiating a bookmark is triggered by the fragment sending a
StatusUiAction.Bookmark
- View model receives this, makes API call, waits for response, emits either
a success or failure state
- Fragment collects success/failure states, updates the UI accordingly
* Don't process favourites via EventHub
* Don't process reblog via EventHub
* Don't process poll votes with EventHub
This removes EventHub from the fragment
* Respond to follow requests via the view model
* Docs and cleanup
* Typo and editing pass
* Minor edits for clarity
* Remove newline in diagram
* Reorder sequence diagram
* s/authorize/accept/
* s/pagingDataFlow/pagingData/
* Add brief KDoc
* Try and fetch a full first page of notifications
* Call the API method `notifications` again
* Log UI errors at the point of handling
* Remove unused variable
* Replace String.format() with interpolation
* Convert NotificationViewData to data class
* Rename copy() to make(), to avoid confusion with default copy() method
* Lint
* Update app/src/main/res/layout/simple_list_item_1.xml
* Update app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsPagingAdapter.kt
* Update app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModel.kt
* Update app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.kt
* Update app/src/main/java/com/keylesspalace/tusky/viewdata/NotificationViewData.kt
* Initial NotificationsViewModel tests
* Add missing import
* More tests, some cleanup
* Comments, re-order some code
* Set StateRestorationPolicy.PREVENT_WHEN_EMPTY
* Mark clearNotifications() as "suspend"
* Catch exceptions from clearNotifications and emit
* Update TODOs with explanations
* Ensure initial fetch uses a null ID
* Stop/start collecting pagingData based on the lifecycle
* Don't hide the list while refreshing
* Refresh notifications on mutes and blocks
* Update tests now clearNotifications is a suspend fun
* Add "Refresh" menu to NotificationsFragment
* Use account.name over account.displayName
* Update app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.kt
Co-authored-by: Konrad Pozniak <connyduck@users.noreply.github.com>
* Mark layoutmanager as lateinit
* Mark layoutmanager as lateinit
* Refactor generating UI text
* Add Copyright header
* Correctly apply notification filters
* Show follow request header in notifications
* Wait for follow request actions to complete, so the reqeuest is sent
* Remove duplicate copyright header
* Revert copyright change in unmodified file
* Null check response body
* Move NotificationsFragment to component.notifications
* Use viewlifecycleowner.lifecyclescope
* Show notification filter as a dialog rather than a popup window
The popup window:
- Is inconsistent UI
- Requires a custom layout
- Didn't play nicely with viewbinding
* Refresh adapter on block/mute
* Scroll up slightly when new content is loaded
* Restore progressbar
* Lint
* Update app/src/main/res/layout/simple_list_item_1.xml
---------
Co-authored-by: Konrad Pozniak <connyduck@users.noreply.github.com>
2023-03-10 20:12:33 +01:00
|
|
|
truth = { module = "com.google.truth:truth", version.ref = "truth" }
|
|
|
|
turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" }
|
2022-11-04 20:10:26 +01:00
|
|
|
unified-push = { module = "com.github.UnifiedPush:android-connector", version.ref = "unified-push" }
|
2023-03-10 20:30:55 +01:00
|
|
|
xmlwriter = { module = "org.pageseeder.xmlwriter:pso-xmlwriter", version.ref = "xmlwriter" }
|
2022-11-04 20:10:26 +01:00
|
|
|
|
|
|
|
[bundles]
|
|
|
|
androidx = ["androidx-core-ktx", "androidx-appcompat", "androidx-fragment-ktx", "androidx-browser", "androidx-swiperefreshlayout",
|
|
|
|
"androidx-recyclerview", "androidx-exifinterface", "androidx-cardview", "androidx-preference-ktx", "androidx-sharetarget",
|
|
|
|
"androidx-emoji2-core", "androidx-emoji2-views-core", "androidx-emoji2-view-helper", "androidx-lifecycle-viewmodel-ktx",
|
2023-06-11 13:17:30 +02:00
|
|
|
"androidx-constraintlayout", "androidx-paging-runtime-ktx", "androidx-viewpager2", "androidx-work-runtime-ktx",
|
Move ExoPlayer initialization to a Dagger module and optimize its dependencies (#4296)
Currently, ExoPlayer is initialized explicitly in `ViewMediaFragment`
with all its dependencies, including many that are not useful for
viewing Mastodon media attachments.
This pull request moves most ExoPlayer initialization and configuration
to a new Dagger module, and instead a `Provider<ExoPlayer>` factory is
injected in the Fragment so it can create new instances when needed.
The following ExoPlayer components will be configured:
- **Renderers**: all of them (audio, video, metadata, subtitles) except
for the `CameraMotionRenderer`.
- **Extractors**: FLAC, Wav, Mp4, Ogg, Matroska/WebM and MP3 containers,
to provide the same support as Firefox or Chrome browsers. Other
container formats that are either image formats (already covered by
Glide), not web-friendly or reserved for live streaming are skipped.
- **MediaSource**: only progressive download (through OkHttp) is
provided. Live streaming support using protocols like RTSP, MPEG/Dash or
HLS is skipped, because Mastodon servers don't use these protocols to
download attachments.
The Mastodon documentation mentions the [supported media formats for
attachments](https://docs.joinmastodon.org/user/posting/#media) and this
covers them and even more. The docs also mentions that the video and
audio files are transcoded to MP4 and MP3 upon upload but that was not
the case in the past (for example WebM was used) and it could change
again in the future.
Specifying these components manually allows reducing the APK size by
about 200 KB thanks to R8 shrinking.
There are also a few extra code changes:
- Remove the code specific to API < 24 since the min SDK of the app is
now 24.
- Add support for pausing a video when unplugging headphones.
- Specify the audio attributes according to content type to help the
Android audio mixer.
2024-03-09 11:04:04 +01:00
|
|
|
"androidx-core-splashscreen", "androidx-activity", "androidx-media3-exoplayer", "androidx-media3-datasource-okhttp",
|
2024-05-10 17:14:26 +02:00
|
|
|
"androidx-media3-ui", "androidx-drawerlayout"]
|
2022-11-04 20:10:26 +01:00
|
|
|
filemojicompat = ["filemojicompat-core", "filemojicompat-ui", "filemojicompat-defaults"]
|
|
|
|
glide = ["glide-core", "glide-okhttp3-integration", "glide-animation-plugin"]
|
|
|
|
material-drawer = ["material-drawer-core", "material-drawer-iconics"]
|
|
|
|
mockito = ["mockito-kotlin", "mockito-inline"]
|
Replace Gson library with Moshi (#4309)
**! ! Warning**: Do not merge before testing every API call and database
read involving JSON !
**Gson** is obsolete and has been superseded by **Moshi**. But more
importantly, parsing Kotlin objects using Gson is _dangerous_ because
Gson uses Java serialization and is **not Kotlin-aware**. This has two
main consequences:
- Fields of non-null types may end up null at runtime. Parsing will
succeed, but the code may crash later with a `NullPointerException` when
trying to access a field member;
- Default values of constructor parameters are always ignored. When
absent, reference types will be null, booleans will be false and
integers will be zero.
On the other hand, Kotlin-aware parsers like **Moshi** or **Kotlin
Serialization** will validate at parsing time that all received fields
comply with the Kotlin contract and avoid errors at runtime, making apps
more stable and schema mismatches easier to detect (as long as logs are
accessible):
- Receiving a null value for a non-null type will generate a parsing
error;
- Optional types are declared explicitly by adding a default value. **A
missing value with no default value declaration will generate a parsing
error.**
Migrating the entity declarations from Gson to Moshi will make the code
more robust but is not an easy task because of the semantic differences.
With Gson, both nullable and optional fields are represented with a null
value. After converting to Moshi, some nullable entities can become
non-null with a default value (if they are optional and not nullable),
others can stay nullable with no default value (if they are mandatory
and nullable), and others can become **nullable with a default value of
null** (if they are optional _or_ nullable _or_ both). That third option
is the safest bet when it's not clear if a field is optional or not,
except for lists which can usually be declared as non-null with a
default value of an empty list (I have yet to see a nullable array type
in the Mastodon API).
Fields that are currently declared as non-null present another
challenge. In theory, they should remain as-is and everything will work
fine. In practice, **because Gson is not aware of nullable types at
all**, it's possible that some non-null fields currently hold a null
value in some cases but the app does not report any error because the
field is not accessed by Kotlin code in that scenario. After migrating
to Moshi however, parsing such a field will now fail early if a null
value or no value is received.
These fields will have to be identified by heavily testing the app and
looking for parsing errors (`JsonDataException`) and/or by going through
the Mastodon documentation. A default value needs to be added for
missing optional fields, and their type could optionally be changed to
nullable, depending on the case.
Gson is also currently used to serialize and deserialize objects to and
from the local database, which is also challenging because backwards
compatibility needs to be preserved. Fortunately, by default Gson omits
writing null fields, so a field of type `List<T>?` could be replaced
with a field of type `List<T>` with a default value of `emptyList()` and
reading back the old data should still work. However, nullable lists
that are written directly (not as a field of another object) will still
be serialized to JSON as `"null"` so the deserializing code must still
be handling null properly.
Finally, changing the database schema is out of scope for this pull
request, so database entities that also happen to be serialized with
Gson will keep their original types even if they could be made non-null
as an improvement.
In the end this is all for the best, because the app will be more
reliable and errors will be easier to detect by showing up earlier with
a clear error message. Not to mention the performance benefits of using
Moshi compared to Gson.
- Replace Gson reflection with Moshi Kotlin codegen to generate all
parsers at compile time.
- Replace custom `Rfc3339DateJsonAdapter` with the one provided by
moshi-adapters.
- Replace custom `JsonDeserializer` classes for Enum types with
`EnumJsonAdapter.create(T).withUnknownFallback()` from moshi-adapters to
support fallback values.
- Replace `GuardedBooleanAdapter` with the more generic `GuardedAdapter`
which works with any type. Any nullable field may now be annotated with
`@Guarded`.
- Remove Proguard rules related to Json entities. Each Json entity needs
to be annotated with `@JsonClass` with no exception, and adding this
annotation will ensure that R8/Proguard will handle the entities
properly.
- Replace some nullable Boolean fields with non-null Boolean fields with
a default value where possible.
- Replace some nullable list fields with non-null list fields with a
default value of `emptyList()` where possible.
- Update `TimelineDao` to perform all Json conversions internally using
`Converters` so no Gson or Moshi instance has to be passed to its
methods.
- ~~Create a custom `DraftAttachmentJsonAdapter` to serialize and
deserialize `DraftAttachment` which is a special entity that supports
more than one json name per field. A custom adapter is necessary because
there is not direct equivalent of `@SerializedName(alternate = [...])`
in Moshi.~~ Remove alternate names for some `DraftAttachment` fields
which were used as a workaround to deserialize local data in 2-years old
builds of Tusky.
- Update tests to make them work with Moshi.
- Simplify a few `equals()` implementations.
- Change a few functions to `val`s
- Turn `NetworkModule` into an `object` (since it contains no abstract
methods).
Please test the app thoroughly before merging. There may be some fields
currently declared as mandatory that are actually optional.
2024-04-02 21:01:04 +02:00
|
|
|
moshi = ["moshi-core", "moshi-adapters"]
|
2022-11-04 20:10:26 +01:00
|
|
|
okhttp = ["okhttp-core", "okhttp-logging-interceptor"]
|
Replace Gson library with Moshi (#4309)
**! ! Warning**: Do not merge before testing every API call and database
read involving JSON !
**Gson** is obsolete and has been superseded by **Moshi**. But more
importantly, parsing Kotlin objects using Gson is _dangerous_ because
Gson uses Java serialization and is **not Kotlin-aware**. This has two
main consequences:
- Fields of non-null types may end up null at runtime. Parsing will
succeed, but the code may crash later with a `NullPointerException` when
trying to access a field member;
- Default values of constructor parameters are always ignored. When
absent, reference types will be null, booleans will be false and
integers will be zero.
On the other hand, Kotlin-aware parsers like **Moshi** or **Kotlin
Serialization** will validate at parsing time that all received fields
comply with the Kotlin contract and avoid errors at runtime, making apps
more stable and schema mismatches easier to detect (as long as logs are
accessible):
- Receiving a null value for a non-null type will generate a parsing
error;
- Optional types are declared explicitly by adding a default value. **A
missing value with no default value declaration will generate a parsing
error.**
Migrating the entity declarations from Gson to Moshi will make the code
more robust but is not an easy task because of the semantic differences.
With Gson, both nullable and optional fields are represented with a null
value. After converting to Moshi, some nullable entities can become
non-null with a default value (if they are optional and not nullable),
others can stay nullable with no default value (if they are mandatory
and nullable), and others can become **nullable with a default value of
null** (if they are optional _or_ nullable _or_ both). That third option
is the safest bet when it's not clear if a field is optional or not,
except for lists which can usually be declared as non-null with a
default value of an empty list (I have yet to see a nullable array type
in the Mastodon API).
Fields that are currently declared as non-null present another
challenge. In theory, they should remain as-is and everything will work
fine. In practice, **because Gson is not aware of nullable types at
all**, it's possible that some non-null fields currently hold a null
value in some cases but the app does not report any error because the
field is not accessed by Kotlin code in that scenario. After migrating
to Moshi however, parsing such a field will now fail early if a null
value or no value is received.
These fields will have to be identified by heavily testing the app and
looking for parsing errors (`JsonDataException`) and/or by going through
the Mastodon documentation. A default value needs to be added for
missing optional fields, and their type could optionally be changed to
nullable, depending on the case.
Gson is also currently used to serialize and deserialize objects to and
from the local database, which is also challenging because backwards
compatibility needs to be preserved. Fortunately, by default Gson omits
writing null fields, so a field of type `List<T>?` could be replaced
with a field of type `List<T>` with a default value of `emptyList()` and
reading back the old data should still work. However, nullable lists
that are written directly (not as a field of another object) will still
be serialized to JSON as `"null"` so the deserializing code must still
be handling null properly.
Finally, changing the database schema is out of scope for this pull
request, so database entities that also happen to be serialized with
Gson will keep their original types even if they could be made non-null
as an improvement.
In the end this is all for the best, because the app will be more
reliable and errors will be easier to detect by showing up earlier with
a clear error message. Not to mention the performance benefits of using
Moshi compared to Gson.
- Replace Gson reflection with Moshi Kotlin codegen to generate all
parsers at compile time.
- Replace custom `Rfc3339DateJsonAdapter` with the one provided by
moshi-adapters.
- Replace custom `JsonDeserializer` classes for Enum types with
`EnumJsonAdapter.create(T).withUnknownFallback()` from moshi-adapters to
support fallback values.
- Replace `GuardedBooleanAdapter` with the more generic `GuardedAdapter`
which works with any type. Any nullable field may now be annotated with
`@Guarded`.
- Remove Proguard rules related to Json entities. Each Json entity needs
to be annotated with `@JsonClass` with no exception, and adding this
annotation will ensure that R8/Proguard will handle the entities
properly.
- Replace some nullable Boolean fields with non-null Boolean fields with
a default value where possible.
- Replace some nullable list fields with non-null list fields with a
default value of `emptyList()` where possible.
- Update `TimelineDao` to perform all Json conversions internally using
`Converters` so no Gson or Moshi instance has to be passed to its
methods.
- ~~Create a custom `DraftAttachmentJsonAdapter` to serialize and
deserialize `DraftAttachment` which is a special entity that supports
more than one json name per field. A custom adapter is necessary because
there is not direct equivalent of `@SerializedName(alternate = [...])`
in Moshi.~~ Remove alternate names for some `DraftAttachment` fields
which were used as a workaround to deserialize local data in 2-years old
builds of Tusky.
- Update tests to make them work with Moshi.
- Simplify a few `equals()` implementations.
- Change a few functions to `val`s
- Turn `NetworkModule` into an `object` (since it contains no abstract
methods).
Please test the app thoroughly before merging. There may be some fields
currently declared as mandatory that are actually optional.
2024-04-02 21:01:04 +02:00
|
|
|
retrofit = ["retrofit-core", "retrofit-converter-moshi"]
|
2022-11-04 20:10:26 +01:00
|
|
|
room = ["androidx-room-ktx", "androidx-room-paging"]
|
2023-03-10 20:30:55 +01:00
|
|
|
xmldiff = ["diffx", "xmlwriter"]
|