This Base Library for Kaluga contains Data types and accessors for easy use across all Kaluga projects.
This library is available on Maven Central. You can import Kaluga Base as follows:
repositories {
// ...
mavenCentral()
}
// ...
dependencies {
// ...
implementation("com.splendo.kaluga:base:$kalugaVersion")
}
You can run code on the OS Main Thread by using runOnMain
. Use the MainQueueDispatcher
to access the Main queue of the OS.
- Use
byOrdinalOrDefault
to get an Enum value by ordinal or the default value if no such value exists. - (Android Only) Use the
ApplicationHolder
class to get and set the currentApplication
. This is useful for default access to the ApplicationContext - (iOS Only) you can flow on Key-Value Observed values by using the
NSObject.observeKeyValueAsFlow(keyPath:options:)
method
- Convert a
ByteArray
to a hexadecimal String usingtoHexString()
- (iOS Only) Convert
NSData
toByteArray
and vice versa usingtoByteArray()
andtoNSData()
respectively.
The Kaluga library offers usage of State Machines.
The StateRepo
functions as a Flow
to manage a state machine.
The states the state repo can be in are described by a KalugaState
implementation.
However, in order to ensure only acceptable state transitions occur it is not possible to directly set a new state.
Instead takeAndChangeState()
should be called.
This method takes a closure that determines the next state based on the current state.
The closure should either return a new state created from the given state or remain
to cancel state transitions.
If additional calls to takeAndChangeState occur while transitioning to a new state they will be enqueued until the pending transition has completed.
It is recommended to only allow KalugaState
classes to create the new state that they can transition into.
sealed class SomeState : KalugaState<SomeState> {
class StateOne internal constructor() : SomeState() {
val becomeTwo = suspend { StateTwo() }
}
class StateTwo internal constructor() : SomeState()
}
val stateRepo: StateRepo<SomeState> = ...
launch {
stateRepo.takeAndChangeState { state ->
when (val state) {
is StateOne -> state.becomeTwo
is StateTwo -> state.remain
}
}
}
During transition the following callbacks may be implemented. The callbacks are called in the order given.
HandleBeforeCreating.beforeCreatingNewState
: Tells the current state that it will be transitioned away from.HandleAfterCreating.afterCreatingNewState
: Tell the current state that it will transition to a new given state.HandleBeforeOldStateIsRemoved.beforeOldStateIsRemoved
: Tells the new state that is is about to be transitioned to. After this call the new state will become the current state.HandleAfterNewStateIsSet.afterNewStateIsSet
: Tells the previous state that the transition has occurred and it is no longer the current state.HandleAfterOldStateIsRemoved.afterOldStateIsRemoved
: Tells the new state that the previous state was removed.
If an atomic action is required on the state useState()
can be called. When called, the state is guaranteed not to change for the duration of the action.
StateRepo may have a variable number of flows observing them. Thus they may at some point (temporarily) lose all observers. To determine what should happen when no observers are available, both flowable and stateRepo can be marked as either stateful (Hot) or stateless (Cold).
A Hot stateRepo gets an initial value upon creation.
This value remains until the flowable is closed.
Implement HotStateRepo
respectively to use this behaviour.
A Cold stateRepo by comparison only has a value when at least one flow is observing it.
Upon creation the cold data stream should provide both an initializer and a deinitializer.
The initializer will be called if the number of flows observing changes from 0 to 1.
The deinitializer will be called when the number of flows observing changes drops to 0.
Use ColdStateRepo
for this behaviour.
Consuming a flow may often take longer than producing it. Kotlin Flows can handle this using buffer
but this progressively increase the time between data being produced and data being consumed.
Kaluga offers a BufferedAsListChannel
to buffer all data produced between consumption into a list. This allows the consumer to deal with groups of data and ideally prevent increasing delays.
The BufferedAsListChannel
is a Channel
that always buffers an unlimited amount of data points.
Kaluga includes a Date
class to manage and compare time.
Dates can be created using either Date.now()
or Date.epoch()
.
Dates are mutable be default and can be compared.
val today = Date.now()
val tomorrow = today.copy().apply {
day += 1
}
assertTrue(today < tomorrow)
Kaluga includes a Decimal
class to manage decimal numbers with high precision.
Decimal can be created from any Number
using toDecimal()
or back to a Double
/Int
using toDouble()
/toInt()
respectively.
Use Decimals to do standard arithmetic operations. A Rounding mode or scale can be provided for these calculations.
It's possible to format to and from some data types using Kaluga.
Date
can be formatted and parsed using aKalugaDateFormatter
Number
can be formatted and parsed using aNumberFormatter
String
can be formatted to include different data types usingStringFormatter