A collection of generalized application system components that can be used in mobile, desktop and web applications to create reusable functionality across platforms.
Regolith is broken up into distinct modules that can be used independently. Each module provides different abstractions on system components common in applications.
Initializers are the start of your application. They can be used to configure libraries, or start services.
com.inkapplications.regolith:init
The interface for an Initializer has a single-method that is called when the application starts:
object DatabaseInitializer: Initializer {
override fun initialize(targetManager: TargetManager) {
Database.init()
}
}
Initializers are then added to an InitRunner
to be started immediately
when initialize()
is called:
fun main() {
val myInitializers = listOf(DatabaseInitializer)
RegolithInitRunner(
initializers = myInitializers
).initialize()
}
All initializers are started immediately on application start. However, Targets may be used to wait for initializers or to pass data between initializers.
To create a target, extend the empty InitTarget
interface:
object DatabaseInitTarget: InitTarget
If your initializer provides data that may be needed by other initializers, it can be passed through the Target as data in the Target object:
data class DatabaseInitTarget(
val session: DatabaseSession
)
Targets can be emitted by any initializer through
targetManager.postTarget()
provided in the initialize
call:
object DatabaseInitializer: Initializer {
override fun initialize(targetManager: TargetManager) {
val session = Database.init()
targetManager.postTarget(DatabaseInitTarget(session))
}
}
You can wait for a Target with the targetManager.awaitTarget()
method:
class TokenRefreshInitializer(
val api: MyApi
): Initializer {
override fun initialize(targetManager: TargetManager) {
// Wait for DatabaseInitTarget and use its provided session data:
val session = targetManager.awaitTarget(DatabaseInitTarget)
session.updateToken(api.fetchToken())
}
}
Targets can also be posted to the manager via the RegolithInitRunner
.
For example, an Android application may use this mechanism to provide
the application instance as a target:
data class ApplicationTarget(
val application: Application
)
class MyApplication: Application() {
private val initializers = listOf(DatabaseInitializer)
private val initRunner = RegolithInitRunner(
initializers = myInitializers
)
override fun onCreate() {
initRunner.initialize()
initRunner.postTarget(ApplicationTarget(this))
}
}
The processes module provices abstractions on long-running components of your application
com.inkapplications.regolith:processes
Daemons are similar to initializers, except that they are intended to never stop running. While initializers may not suspend and must return for initialization to complete, Daemons cannot be completed.
To create a Daemon, implement the Daemon
interface:
class WebServerDaemon(
private val server: WebServer
): Daemon {
suspend fun startDaemon(): Nothing {
server.start()
throw IllegalStateException("Server exited unexpectedly")
}
}
Daemons can be started with the DaemonInitializer
which can be run
as a part of the init
component:
fun main() {
val daemonInitializer = DaemonInitializer(
daemons = listOf(webServerDaemon)
)
val myInitializers = listOf(daemonInitializer)
RegolithInitRunner(
initializers = myInitializers
).initialize()
}
Crons are similar to Daemons, except that they are expected to run and complete on a predetermined time schedule.
Crons can be created by implementing the CronJob
interface:
class DatabaseFlushCron(
private val database: Database
): CronJob {
override val schedule = Schedule().withMinutes { it % 10 } // Every 10 minutes
suspend fun runCron(time: LocalDateTime, zone: TimeZone) {
database.flush()
}
}
Crons can be run automatically by using the CoroutineCronDaemon
:
fun main() {
val cronDaemon = CoroutineCronDaemon(
jobs = DatabaseFlushCron(myDatabase),
)
val daemonInitializer = DaemonInitializer(
daemons = listOf(webServerDaemon, cronDaemon)
)
val myInitializers = listOf(daemonInitializer)
RegolithInitRunner(
initializers = myInitializers
).initialize()
}
The resources module provides abstractions around application resources like files and strings.
com.inkapplications.regolith:resources
The StringResources
interface provides a way to load localized strings
in an application.
Strings can be Identified either by string or integer, and supports parameter formatting.
Android has a default implementation to its internal string localization
loader via the AndroidStringResources
class.
The FileResources
interface provides a way to load files from the
filesystem or the local application resources.
Application packaged resources can be loaded with by name or ID with the
FileResources.Local
id type. External files can be loaded by path or URI.
Android has a default implementation via the AndroidFileResources
class.
JVM has a default implementation via the JvmFileResources
class.
Testing implementations are provided in the doubles
package, including:
DummyResources
, StubResources
, and ParrotResources
.