Depending on Native Implementations
Once again the game logic looks like some kind of code which could be shared between Android and iOS. So let’s try to move this into our common project.
class Game {
var afterNextGenerationCalculated: () -> Unit = {}
val board: Board
private var timer: GolTimer? = null
init {
val columns = 30
val rows = 50
board = Board(columns, rows)
board.setCells(
"""
***_*
*____
___**
_**_*
*_*_*
""".trimIndent().cells().translatedTo(columns / 2 - 2, rows / 2 - 2)
)
}
fun start() {
if (timer != null) return
timer = GolTimer {
board.calculateNextGeneration()
afterNextGenerationCalculated()
}
}
fun stop() {
timer?.stop()
timer = null
}
}
Looks good, but there is a problem. The Kotlin standard library does not provide a Timer class or similar which we could use to implement our game loop.
Kotlin Multi Platform has a nice solution for this kind of problems. If we
mark a class or a function with the keyword expect
we can use it in the common code but without
a multi platform implementation. Think of it as an interface which is required to have an
implementation in all platforms configured as targets.
expect class GolTimer(action: () -> Unit) {
fun stop()
}
We then implement the actual
class in the Java and the iOS specific code using the tools
that these platforms provide.
import kotlin.concurrent.fixedRateTimer
actual class GolTimer actual constructor(action: () -> Unit) {
private val timer: Timer =
fixedRateTimer("GolTimer", false, period = 500L) {
action()
}
actual fun stop() {
timer.cancel()
}
}
import platform.Foundation.NSTimer
actual class GolTimer actual constructor(action: () -> Unit) {
private val nativeTimer: NSTimer =
NSTimer.scheduledTimerWithTimeInterval(1.0, true) {
action()
}
actual fun stop() {
nativeTimer.invalidate()
}
}
Note that in the implementation for iOS we get access to the iOS APIs from Kotlin.
Now we can use the common Game class in the iOS and Android project.
class MainActivity : AppCompatActivity() {
private val game = Game()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
boardView.cellSize = 14.0f * resources.displayMetrics.density
boardView.board = game.board
game.afterNextGenerationCalculated = { boardView.invalidate() }
game.start()
}
override fun onResume() {
super.onResume()
game.start()
}
override fun onPause() {
super.onPause()
game.stop()
}
}
class BoardStore: ObservableObject {
let objectWillChange = ObservableObjectPublisher()
var board: Board? = nil
func calculateNextGeneration() {
self.objectWillChange.send()
}
}
struct ContentView: View {
let game = Game()
@ObservedObject var boardStore = BoardStore()
var body: some View {
BoardView(board: $boardStore.board)
.onAppear(perform: {
self.boardStore.board = self.game.board
self.game.afterNextGenerationCalculated = {
self.boardStore.calculateNextGeneration()
}
self.game.start()
})
.onDisappear(perform: {
self.game.stop()
})
}
}