Writing patches
API: Settings
Settings the user changes inside the patched app, and the patched code that reads them. The guide page is Settings inside the app. Everything here is in app.reseam.patch.settings.
Your extensions read the same settings through generated objects. Those are listed in API: App runtime.
Declaring settings
SettingsGroup
The base of an object that declares settings as properties.
object AppSettings : SettingsGroup(ExampleStrings) {
val hideAds by toggle(default = true)
}
| Member | Description |
|---|---|
SettingsGroup(strings: StringTable, keyPrefix: String? = null) |
strings holds the settings' titles. keyPrefix starts every key; it defaults to the object's name in snake case, so AppSettings gives app_settings. Give two objects the same prefix to split one set of settings without changing their keys. |
strings: StringTable |
The table the titles come from. |
A setting's title is the string named after its property in snake case: hideAds reads hide_ads. Its summary is hide_ads_summary, when the table has one. A missing title fails the build.
Setting functions
Use these as property delegates inside a SettingsGroup. key defaults to the group's prefix and the property name, so AppSettings.hideAds is stored as app_settings.hide_ads.
| Function | Description |
|---|---|
toggle(default: Boolean, key: String? = null, icon: Vector? = null, covers: List<ToggleSetting> = emptyList()) |
An on/off switch. While it is on, each toggle in covers reads as on and shows greyed out. Their own values come back when it is turned off. |
text(default: String, multiline: Boolean = false, key: String? = null, icon: Vector? = null) |
Text the user types. A multiline text holds one entry per line. |
folder(default: String, key: String? = null, icon: Vector? = null) |
A folder the user picks. The app gets permission to read and write it. |
choice(default: String, choices: List<Choice>, key: String? = null, icon: Vector? = null) |
One of several values. default must be one of the choices' values. |
Warning
Pin your keys before the first release. If you later rename the object or the property, the key changes, and every user's saved value is lost. Set key = "..." on each setting, and you can rename the code freely.
SettingDelegate
What the setting functions return: a property delegate that works out the key from the property name. You never use it directly.
Setting
The base of every setting.
| Member | Description |
|---|---|
key: String |
Where the value is stored. Keys only have to be unique within your bundle, since each bundle stores its settings apart. |
title: StringRef |
The title on the settings screen. |
summary: StringRef? |
The line under the title, if any. |
default: T |
The value until the user changes it. |
icon: Vector? |
The icon at the start of the row, if any. |
ToggleSetting
An on/off setting. covers: List<ToggleSetting> is the toggles it switches on while it is on.
TextSetting
A text setting. multiline: Boolean says whether it holds one entry per line.
FolderSetting
A folder setting. Its value is the folder's address, as text.
ChoiceSetting
| Member | Description |
|---|---|
choices: List<Choice> |
The values the user picks from. |
copy(default: String = this.default, choices: List<Choice> = this.choices): ChoiceSetting |
The same setting, under the same key, with other choices. A patch uses it to offer choices that depend on its options. |
Choice
Choice(value: String, title: DisplayText): the stored value, and the title users see.
Text
StringTable
Translated strings, generated from the strings.xml files in a settings module's src/main/strings. The generated object has one StringRef member per string, named in camel case: feed_style_compact becomes ExampleStrings.feedStyleCompact.
| Member | Description |
|---|---|
id: String |
The table's name in the app. |
StringRef
One string of a table, shown in the app's current language.
| Member | Description |
|---|---|
table: StringTable |
Its table. |
name: String |
Its name in strings.xml. |
Verbatim
Verbatim(text: String): text shown as it is in every language, such as a name the user typed into an option.
DisplayText
Text a settings screen shows: a StringRef or a Verbatim.
Laying out the screen
SettingsSection
A group of settings under a heading.
| Member | Description |
|---|---|
title: StringRef |
The heading. |
settings: List<Setting<*>> |
The settings, in order. |
page: SettingsPage? |
The page it is on, or null for the main screen. |
section
| Function | Description |
|---|---|
section(title: StringRef, vararg settings: Setting<*>): SettingsSection |
A section on the main screen. |
section(page: SettingsPage, title: StringRef, vararg settings: Setting<*>): SettingsSection |
A section on a page. Sections with the same page and title merge, so several patches can add to one section. |
SettingsPage
A page of its own, shown as a row on its parent.
| Member | Description |
|---|---|
SettingsPage(id: String, title: StringRef, parent: SettingsPage? = null, order: Int = 0, summary: StringRef? = null, icon: Vector? = null) |
parent nests it in another page. Lower order comes first among its siblings. |
id, title, parent, order, summary, icon |
As above. |
Only pages with settings from patches that ran appear, along with their parents.
The settings host
settingsHost
settingsHost(appId: String, block: SettingsHostBuilder.() -> Unit): SettingsHost declares the internal patch that adds your bundle's settings screen to one app. Each bundle has one host per app. appId is a short name for the app, used in the host patch's description: settingsHost("example") is described as "Settings for example".
SettingsHostBuilder
The receiver of settingsHost { }. It has compatibleWith and dependsOn from PatchDeclaration, plus:
| Member | Description |
|---|---|
settings(vararg sections: SettingsSection) |
Sections the host itself owns, for settings that belong to no single patch. They lead the screen. |
strings(vararg tables: StringTable) |
String tables your extensions read. The tables your settings use ship anyway. |
brand(brand: Brand) |
How the settings screen and your branded components look. |
about(about: About) |
Links and buttons on the screen's about card. |
install(block: PatchRuntime.() -> Unit) |
Connects the screen to the app, typically a row in the app's own settings that calls openSettings(). It runs once every patch has registered its settings. |
SettingsHost
What settingsHost returns: a ReseamPatch, with all its members, that you pass to PatchBuilder.settings.
| Member | Description |
|---|---|
appId: String |
The app's short name. |
register(patch: ReseamPatch, sections: List<SettingsSection>) |
Adds a patch's sections. PatchBuilder.settings calls it for you. |
What the host does when it runs: it writes your bundle's settings, string tables, and brand into the app under assets/reseam/<bundle>/, adds the app runtime, and starts it first in the Application's onCreate. The app needs an Application class.
Gates
gate
gate(setting: ToggleSetting, block: GateScope.() -> Unit) applies the hooks written in the block only while the toggle is on. The hooked code reads the toggle each time it runs.
A Hermes hook in a gate reads the toggle once per process, through the React Native module that addReactPackage adds.
GateScope
The receiver of gate { }. Each member works like the same hook outside a gate.
| Member | Description |
|---|---|
MethodTarget.before { }, MethodTarget.after { } |
Code at the start of a method, or before its returns. |
PointTarget.before { }, PointTarget.after { } |
Code around an instruction. |
PointTarget.skip() |
Skips the call at the point. Its result must be unused. |
MethodTarget.alwaysReturn(), alwaysReturn(value: Boolean / Int / Long / String), alwaysReturnNull() |
Returns at once with this value. |
FunctionTarget.before { }, after { }, replace { } |
Hermes hooks. |
FunctionTarget.alwaysReturn(), alwaysReturn(value: Boolean / Int / String), alwaysReturnNull() |
Hermes constant returns. |
FunctionTarget.setArgument(index: Int, path: String, value: Boolean) |
Calls the JavaScript function with a copy of argument index whose property at path is value. path names nested properties with dots, such as options.compact. Each object on the way is copied, so the caller's object doesn't change. |
setting: ToggleSetting |
The gate's toggle. |
whenEnabled
| Function | Description |
|---|---|
CodeScope.whenEnabled(setting: ToggleSetting, block: CodeScope.() -> Unit): Otherwise |
Inside a code block: runs the block in the app when the toggle is on. |
JsCodeScope.whenEnabled(setting: ToggleSetting, block: JsCodeScope.() -> Unit): JsOtherwise |
The same, in a Hermes code block. |
Reading settings in code blocks
valueOf reads a setting's current value in the app. Before the app's Application.onCreate has run, such as in a content provider, it reads the default.
| Function | Description |
|---|---|
CodeScope.valueOf(setting: ToggleSetting): ValueRef |
A boolean. |
CodeScope.valueOf(setting: TextSetting): ValueRef |
A String. |
CodeScope.valueOf(setting: FolderSetting): ValueRef |
The folder's address, as a String. |
CodeScope.valueOf(setting: ChoiceSetting): ValueRef |
The chosen value, as a String. A stored value the setting no longer lists reads as the default. |
JsCodeScope.valueOf(setting: ToggleSetting): JsValue |
A toggle's value, in a Hermes code block. |
The app runtime in code blocks
| Member | Description |
|---|---|
CodeScope.openSettings(): ValueRef |
Opens your bundle's settings over the app's current screen. It works from any thread. |
XmlElement.opensSettings() |
Makes an <intent> element, such as the one an androidx Preference starts, open your settings. Also redirect the row's startActivity call to Reseam.startActivity, which recognizes the intent. |
CodeScope.addReactPackage(packages: ValueRef): ValueRef |
Adds the React Native package that Hermes gates read settings through to packages, the app's package list. It is added once, however many bundles call it. |
Reseam
The app runtime's entry class, as an ExtClass, for patches to call.
| Member | Description |
|---|---|
startActivity: ExtMethod |
startActivity(Context, Intent): opens your settings for an intent opensSettings set up, and starts any other intent as usual. Redirect a settings row's startActivity call to it. |
Branding
Brand
How your settings screen and branded components look. The screen follows the system's light or dark theme, and before Android 10, which has none, the app's own, until the user picks one with the button at the end of its top bar. A brand has a color scheme for each.
| Member | Description |
|---|---|
Brand(name: String, light: ColorScheme, dark: ColorScheme, logo: Logo? = null, font: Font? = null, typography: Typography = Typography()) |
name shows on the about card and can't be blank. |
name, light, dark, logo, font, typography |
As above. |
Brand.DEFAULT |
Material 3's baseline colors and type, in the system font, without a logo. A host without brand uses it. |
ColorScheme
The Material 3 color roles the app runtime paints with. Every role is required: primary, onPrimary, primaryContainer, onPrimaryContainer, secondaryContainer, onSecondaryContainer, surface, surfaceContainerLow, surfaceContainer, surfaceContainerHigh, surfaceContainerHighest, onSurface, onSurfaceVariant, outline, outlineVariant, error, onError, errorContainer, onErrorContainer, and scrim.
Color
An sRGB color. Color.hex(value: String): Color reads #rgb, #rrggbb, or #aarrggbb, with alpha first.
Typography
The Material 3 type roles: headlineSmall, titleLarge, titleMedium, bodyLarge, bodyMedium, labelLarge, and labelMedium. Each defaults to Material 3's size and weight, so override only the roles you want different: Typography(titleLarge = TextStyle(22f, 28f, 600)).
TextStyle
TextStyle(size: Float, lineHeight: Float, weight: Int, tracking: Float = 0f): size, line height, and letter spacing in sp, and a weight from 1 to 1000.
Font
Font(file: ByteArray): a TrueType or OpenType file, read from your patch module's resources. A variable font with a wght axis renders every weight the type roles name.
Logo
Logo(light: Vector, dark: Vector = light): your mark for each theme.
Vector
A vector image, such as an icon or a logo, drawn at any size.
Vector.svg(source: String): Vector reads an SVG when your patches load. The svg element needs a viewBox, or a width and height, with a positive size. These elements draw: path, circle, ellipse, rect, line, polyline, and polygon, inside any number of g. Paint comes from fill, stroke, stroke-width, stroke-linecap, stroke-linejoin, and fill-rule, inherited from enclosing elements. currentColor takes the color the screen draws icons in, so line icons such as Lucide work as they are. A #rgb or #rrggbb color stays fixed, and none paints nothing. Anything else, such as transforms, style, gradients, or text, fails the build.
private object Icons
val megaphone = Vector.svg(Icons::class.java.getResource("/icons/megaphone.svg")!!.readText())
About
About(links: List<Link> = emptyList(), actions: List<Link> = emptyList()): where people reach you, on the settings screen's about card. links show as icon buttons under your name, and actions as buttons below them.
Link
Link(label: DisplayText, url: String, icon: Vector): one link. url must be an https:// address. An icon button shows no label, but screen readers read it aloud.