Download

SDK releases

Upgrade to SDK 3.0

SDK 3.0 changed the source interfaces, and Katari no longer loads extensions built with SDK 2.x. Rebuild one with the changes below, in order.

Build against 3.0 #

Move the SDK to sdk-3.0.0 and start the release count over at 3.0.0, as Version numbers describes. If the build names Injekt itself, it is now uy.kohesive.injekt:injekt-core:

build.gradle.kts
defaultConfig {
    versionCode = 12
    versionName = "2.6.4"
    versionCode = 13
    versionName = "3.0.0"
}

dependencies {
    compileOnly("com.github.pa2x2.katari:entry-source-api:sdk-2.6.0")
    compileOnly("com.github.mihonapp:injekt:91edab2317")
    compileOnly("com.github.pa2x2.katari:entry-source-api:sdk-3.0.0")
    compileOnly("uy.kohesive.injekt:injekt-core:1.16.1")
}

Katari now loads kotlin.* from its own copy whatever the APK carries, so leave the standard library out. Code that calls something newer than the Kotlin Katari ships fails when it runs. What Katari provides at runtime lists the version.

gradle.properties
android.useAndroidX=true
kotlin.stdlib.default.dependency=false

In your store's index, set the extension's extensionLib to "3.0" along with its new version, as The index describes.

Fix what no longer compiles #

Details and chapters come from refresh

getContentDetails, getChapterList and IncrementalChapterSource are gone. Implement refresh, which receives the chapters Katari has stored and says which parts to fetch. More in Refreshing a title.

Kotlin
override suspend fun getContentDetails(entry: SEntry): SEntry =
    work(entry.url).toEntry()

override suspend fun getChapterList(entry: SEntry): List<SEntryChapter> =
    work(entry.url).toChapters()
override suspend fun refresh(
    entry: SEntry,
    existingChapters: List<SEntryChapter>,
    fetchDetails: Boolean,
    fetchChapters: Boolean,
): EntryRefresh {
    val work = work(entry.url)
    return EntryRefresh(
        entry = if (fetchDetails) work.toEntry() else entry,
        chapters = if (fetchChapters) work.toChapters() else existingChapters,
    )
}

Subtitles come with each stream

SubtitleSource is gone. Put the tracks in each VideoStream's subtitles. More in Subtitles.

Kotlin
override suspend fun getSubtitles(
    chapter: SEntryChapter,
    selection: PlaybackSelection,
): List<VideoSubtitle> = episode(chapter).tracks.map { it.toSubtitle() }

VideoStream(
    request = VideoRequest(episode.playlistUrl),
    type = VideoStreamType.HLS,
    subtitles = episode.tracks.map { it.toSubtitle() },
)

Related entries come in groups

getRelatedEntries returns RelatedEntryGroups. Return one group with a null title, or one group per kind of relation with the site's own heading, such as Sequels. More in Optional capabilities.

Kotlin
override suspend fun getRelatedEntries(entry: SEntry): List<SEntry> =
    recommendations(entry)
override suspend fun getRelatedEntries(entry: SEntry): List<RelatedEntryGroup> =
    listOf(RelatedEntryGroup(title = null, entries = recommendations(entry)))

A stream's request can be null

VideoStream.request is a VideoRequest?. Code that reads it has to handle null, which marks a stream whose link a DeferredStreamSource finds later. More in Links found later.

Your own SEntry and SEntryChapter classes

A class that implements them needs the new members: backgroundUrl and merged on SEntry, summary, previewUrl, kind, group and dateText on SEntryChapter. Sources that use SEntry.create() and SEntryChapter.create() need no change.

Book document helpers

BookDocumentStyle.withFlow, BookDocumentInlineStyle.withTextContext and BookDocumentImage.withAccessibility are gone. Their values are now constructor parameters, so use copy or the constructor. BookDocumentStyle has lost backgroundArgb, border and paddingEm too: a block's background, borders and padding belong to the BookDocumentBox around it, in its content's boxes. More in Books.

Kotlin
val body = style.withFlow(indented)
val note = BookDocumentInlineStyle.withTextContext(italic, japanese)
val rule = BookDocumentImage.withAccessibility("rule", null, 400, 8, decorative = true)
val body = style.copy(flow = indented)
val note = italic.copy(textContext = japanese)
val rule = BookDocumentImage("rule", null, 400, 8, decorative = true)

Get JavaScriptEngine from Katari

JavaScriptEngine is an abstract class that Katari implements, so it can no longer be created with a Context. Inject it instead. More in Services.

Kotlin
private val engine = JavaScriptEngine(Injekt.get())
private val engine: JavaScriptEngine by injectLazy()

Helpers removed from core-common

  • stringResource, pluralStringResource and toast(StringResource) read Katari's own strings, which are no longer shared. Keep the extension's text in the extension.
  • The top-level launchUI, launchIO and launchNow launched in GlobalScope. Launch in a CoroutineScope of your own with the extension functions of the same names.

Drop supportsImmersiveFeed

Delete the override. The immersive view now works with every source's comics and video.

Check what still compiles #

Name the arguments of PlaybackSelection and PlaybackDescriptor

PlaybackSelection has a new second parameter, serverKey, and PlaybackDescriptor a new third, servers. Positional arguments after them now fill the wrong ones without an error. More in Dubs, servers and qualities.

Kotlin
PlaybackSelection("jp", "1080")
PlaybackSelection(dubKey = "jp", streamKey = "1080")

Read settings each time you use them

Each profile now has its own source settings. A value read once and kept belongs to whichever profile was open at the time. More in Settings.

Kotlin
private val preferences = getSourcePreferences()
private val token = preferences.getString("token", "")
private val token: String?
    get() = getSourcePreferences().getString("token", "")

Don't register types with Injekt

Katari's Injekt is read-only and resolves only the services it shares. Registering a type throws UnsupportedOperationException, and asking for one it doesn't share throws InjektionException. Create your own objects in the source. More in Services.

Expect the local network to be refused

On Android, Katari asks for the local network permission when a source connects to an address on the user's network. Without it, the request fails with LocalNetworkAccessDeniedException, an IOException the user sees. More in Requests.

The SDK changelog lists every change in 3.0, including what it adds.