Hi ! Let's say I'm building a kind of `Restartable...
# compose
l
Hi ! Let's say I'm building a kind of
Restartable
Composable
. This component needs to be restarted when the user makes a restart request. In previous Android development, this could be achieved by calling on of
Restartable
methods, if
Restartable
was designed as a class. But, talking about
Composition
, what should be a good pattern to achieve this. More on my attempt in thread.
πŸ™Œ 1
For now, the way I'm doing is the following (Each time I need to restart I pass a random value for startId)
Copy code
@Composable
fun Restartable(
startId: Long = 1L
) {

var currentStartId by rememberSaveable{mutableStateOf(startId)}

fun restart() {
...
}

if (currentStartId != startId) {
   restart()
   currentStartId = startId
}

}
Is this a good pattern or can I do better ? Is this in the spirit of Jetpack Compose ?
a
No, it looks as if
restart
is a side effect that you're triggering. If the user requested an operation, perform that operation in response to the user input event handler directly. That operation changes app data, and app data changing may cause recomposition to update your UI. Don't use recomposition itself as an event handler.
πŸ‘ 1
l
Thank you, I understand it's a bad pattern. But what should I do in such case instead ?
If i try
Copy code
@Composable
fun Restartable(
restart: () -> Unit,
) {
}
Then I don't know how to "call" the
restart
callback from the caller code.
a
What is a restart in this context? What is being restarted and when? in response to what?
l
This example is not relevant. So I'll take the
DynamicChessBoard
that I'm developping for my project.
DynamicChessBoard
is an interactive chess board composable, but with an initial position as string (in Forsyth-Edwards Notation). So this chess board has a startPosition as parameter, as well as a mutableState<String>, stored with
rememberSaveable
. But by a click on an external button, a user may request to start a new game from the initial position. Let's call this the
restart event
. How can make a click on the
RestartButton
make the
DynamicChessBoard
restart the game ? If I code inside
DynamicChessBoard
a
restartGame
function, I won't be able to access it from outside. So what should I do instead. Sorry, I did not post the code for
DynamicChessBoard
as it is very complex as now and very long.
a
I see. This is when it's often useful to hoist state into a class that is passed to the composable function as a parameter and
remember {}
/`rememberSaveable {}`d by the caller. A
restartGame
function can exist on that class and the UI can call it.
πŸ‘ 1
Alternatively if you have one state object you could keep its reference in a
mutableStateOf
and replace the old state with a fresh/initial one when the restart game button is clicked
πŸ‘ 1
either way, the event handler for the user requesting a restart should be what performs the reset rather than a recomposition while the app is in a, "please reset me when you get around to it" state
πŸ‘ 1
l
Thank you πŸ™‚ So I'll try with either more state hoisting, or with a state object inside a
mutableStateOf
πŸ™‚
πŸ‘ 1
I managed with something like this :
Copy code
enum class NewGameRequestState {
    REQUEST_PENDING,
    NO_PENDING_REQUEST,
}

@Composable
fun DynamicChessBoard(
...
newGameRequest: NewGameRequestState = NewGameRequestState.NO_PENDING_REQUEST,
newGameRequestProcessedCallback : () -> Unit
...
) {
val aNewGameIsRequested = newGameRequest == NewGameRequestState.REQUEST_PENDING
if (aNewGameIsRequested) {
  startNewGame()
  newGameRequestProcessedCallback()
}
}
And calling the
DynamicChessBoard
like this :
Copy code
var requestNewGameState by remember{mutableStateOf(NewGameRequestState.NO_PENDING_REQUEST)}

fun requestANewGame() {
  requestNewGameState = NewGameRequestState.REQUEST_PENDING
}

DynamicChessBoard(
newGameRequest = requestNewGameState,
newGameRequestProcessedCallback = {
// we clear the newGameRequestState
newGameRequestState =  NewGameRequestState.NO_PENDING_REQUEST})
a
this is still something to avoid. This code is still using recomposition as an event handler.
l
Ok. So I did not understood your previous advice. Indeed, reading your post again, I notice that I am just using an enumeration, instead of a class/object for holding the state. I tried a state hoisting, but the wrong way.
I think I've understood how to use state hoisting efficiently.πŸ˜€
a
Getting there! Change
Copy code
class PositionHandler(startPosition: String) {
	var currentPosition: String = "8/8/8/8/8/8/8/8 w - - 0 1"
		private set
to
Copy code
class PositionHandler(
    private val startPosition: String = "8/8/8/8/8/8/8/8 w - - 0 1"
) {
	var currentPosition: String by mutableStateOf(startPosition)
		private set
and change
Copy code
@Composable
fun DynamicChessBoard(
	...
	positionHandler: PositionHandler
	...
) {
	...
	var position by remember{ mutableStateOf(positionHandler) }
to
Copy code
@Composable
fun DynamicChessBoard(
	...
	position: PositionHandler = remember { PositionHandler() }
	...
) {
	...
πŸ‘ 1
l
Thank you very much πŸ™‚
In the spirit of state hoisiting, is it a good pattern to set up callback in
DynamicChessBoard
for the host
Composable
to update its hoisted state itself in reaction to some
DynamicChessBoard
events ? For example :
Copy code
var gameInProgress by remember{mutableStateOf(false)}

DynamicChessBoard(
   gameInProgress = gameInProgress
   gameEndedCallback = {
     if (it != GameStatus.GOING_ON) gameInProgress = false
   } 
)

@Composable
fun DynamicChessBoard(
gameInProgress: Boolean,
gameEndedCallback : (GameStatus) -> Unit,
) {
fun handleDragAndDrop() {
   if (gameInProgress) {
      // handle drag and drop
   }
}

fun checkGameEndedStatus() {
   val gameEndedStatus = /* */
   if (gameEndedStatus != GameEndedStatus.GOING_ON) {
     gameEndedCallBack(gameEndedStatus)
   }
}

}
a
you could, sure
πŸ‘ 1
l
Thank you very much πŸ™‚