I have almost always used StateFlows. I started to do some research on how to more efficiently create a way to address API called and UI updates. I saw how Flows were mostly used in this process so I decided to start working with them.
Out of the box, I didn’t want to dive into a Retrofit project to start learning. The best way I found to learn new implementations, when possible, is the base UI. Meaning text, buttons and colors.
I derived the below code from the following post that explains a bit more and has a better working example.
https://www.geeksforgeeks.org/kotlin-flow-in-android-with-example
My code below is kind of sloppy. I didn’t organize the different classes. I put them all in one file and a separate for the ViewModel. To make it more neat, you may be better to separate the different classes.
Code
This is the working functionality of the Flow. The design is to change the screen color and text every sec.
data class BGColor (
val color : Color?,
val message : String?
)
data class ColorApiState<out T>(val status: Status, val data: T?) {
companion object {
fun <T> success(data: T?): ColorApiState<T> {
return ColorApiState(Status.SUCCESS, data)
}
fun <T> loading(data: T?): ColorApiState<T> {
return ColorApiState(Status.LOADING, data)
}
fun <T> error(data: T?): ColorApiState<T> {
return ColorApiState(Status.ERROR, data)
}
}
}
enum class Status {
SUCCESS,
LOADING,
ERROR
}
class ApiTest {
private val list = listOf(Color.Red,Color.Blue,Color.Black,Color.White,Color.Yellow)
fun getColor() : BGColor {
return BGColor(list[Random.nextInt(0, list.size)], "")
}
}
class ColorRepository(private val apiTest: ApiTest) {
suspend fun getColor(): Flow<ColorApiState<BGColor>> {
return flow {
val color = apiTest.getColor()
Log.d("ColorAPiState", "${color.color}")
when(color.color){
Color.White -> { emit(ColorApiState.loading(color.copy(message = "Loading"))) }
Color.Black -> { emit(ColorApiState.error(color.copy(message = "Error"))) }
Color.Red -> { emit(ColorApiState.error(color.copy(message = "Error")))}
Color.Blue -> { emit(ColorApiState.success(color.copy(message = "Success"))) }
Color.Yellow -> { emit(ColorApiState.success(color.copy(message = "Success"))) }
}
}.flowOn(Dispatchers.IO)
}
}
Now the ViewModel. Here I will post the entire file
class ViewModel : ViewModel() {
private val colorRepository = ColorRepository(ApiTest())
val colorState = MutableStateFlow(
ColorApiState(
Status.LOADING,
BGColor(null, null))
)
init {
getColor()
}
fun getColor(){
viewModelScope.launch {
while(true) {
colorRepository.getColor()
.collect {
Log.d("ViewModel", "Setting: ${it.data?.color}")
when(it.status){
Status.SUCCESS -> {
colorState.value = ColorApiState.success(
it.data
)
}
Status.LOADING -> {
colorState.value = ColorApiState.loading(
it.data
)
}
Status.ERROR -> {
colorState.value = ColorApiState.error(
it.data
)
}
}
}
Log.d("ColorAPiState", "Looping...")
delay(1000)
}
}
}
}
class ViewModelFactory() : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return if(modelClass.isAssignableFrom(com.itgeek25.sampleflows.viewmodel.ViewModel::class.java)){
com.itgeek25.sampleflows.viewmodel.ViewModel() as T
} else {
throw IllegalArgumentException("ViewModel Not Found!!")
}
}
}
Finally the MainActivity
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val factory = ViewModelFactory()
val viewModel = ViewModelProvider(
this,
factory
)[ViewModel::class.java]
setContent {
var color by remember { mutableStateOf(BGColor(Color.White, "Hello")) }
LaunchedEffect(Unit) {
viewModel.colorState.collect {
it.data?.let {
color = it
Log.i("In Activity", "$color")
}
}
}
SampleFlowsTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Test(
modifier = Modifier.padding(innerPadding),
color
)
}
}
}
}
}
@Composable
fun Test(
modifier: Modifier,
color: BGColor?
) {
val newColor = if(color != null) color.color else Color.Yellow
val message = if(color != null) color.message else "Problem..."
Column(
modifier = modifier
.fillMaxSize()
.background(newColor!!),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = message!!, color = if(newColor == Color.Black) Color.Red else Color.Black, fontSize = 50.sp)
}
}
Simple, short and sweet. Hope this helps and gives better context of implementation. It helped me laying it out as simple as possible for practice without needing to add in any network operations for now.

BONUS
Below I decided to post a more practical example actually using Retrofit.
The project I started to build this from way a JWT project so disregard the Token naming convention. You can name it whatever you want.
The code will be a bit sloppy but it gets the point across and looks very easy to read. Hope it is useful for the next person.
This is the Token classes, sloppy but I sandwiched it all together. Separating it could help with better readability.
class TokenRepository(val context : Context){
suspend fun getJWT() : Flow<TokenApiState<Any>> {
return flow {
emit(TokenApiState.loading("Starting to load..."))
delay(300)
val service = RetrofitHelper.getInstance(context).create(AccessApi::class.java)
val response : Response<Token> = service.altertestJWT("Taco Tuesday is now on Wednesday!")
if(response.isSuccessful){
val result = response.body()
if(result?.token == null){
emit(TokenApiState.expired("Sloppy coding"))
} else {
emit(TokenApiState.success(result))
}
} else {
emit(TokenApiState.expired("Sloppy coding"))
}
}.flowOn(Dispatchers.IO)
}
}
data class TokenApiState<out T>(val status : TokenStatus, val data : T?) {
companion object {
fun <T> success(data: T?): TokenApiState<T> {
return TokenApiState(TokenStatus.VALID, data)
}
fun <T> loading(message : T?): TokenApiState<T> {
return TokenApiState(TokenStatus.LOADING, message)
}
fun <T> expired(message : T?): TokenApiState<T> {
return TokenApiState(TokenStatus.EXPIRED, message)
}
}
}
enum class TokenStatus {
LOADING,
VALID,
EXPIRED
}
data class Token (
val token : String?
)
Now in the AccessAPI, again I was starting a JWT project so disregard naming convention. The $Header call is important on the PHP server side though.
interface AccessApi {
@POST("test_jwt.php")
suspend fun altertestJWT(@Header("Authorization") message : String): Response<Token>
}
Now the ViewModel
class ViewModel(val applicationContext : Context) : ViewModel() {
private val tokenRepository = TokenRepository(applicationContext)
val tokenState = MutableStateFlow(
TokenApiState<Token>(TokenStatus.LOADING, Token(null))
)
fun getToken(){
viewModelScope.launch {
tokenRepository.getJWT().collect{
Log.i("Collect", it.status.name + " : " + it.data)
when(it.status){
TokenStatus.VALID -> { tokenState.value = TokenApiState.success(it.data as Token) }
TokenStatus.LOADING -> { tokenState.value = TokenApiState.loading(Token(it.data as String)) }
TokenStatus.EXPIRED -> { tokenState.value = TokenApiState.expired(Token(it.data as String)) }
}
}
}
}
Finally a simple UI, terrible looking but functions
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val factory = ViewModelFactory(applicationContext)
val viewModel = ViewModelProvider(this, factory)[ViewModel::class.java]
enableEdgeToEdge()
setContent {
SampleJWTTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
var token by remember { mutableStateOf(Token("")) }
LaunchedEffect(Unit) {
viewModel.tokenState.collect {
it.data?.let {
token = it
Log.i("In Activity", "$token")
}
}
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
.background(Color.Gray),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
token.token?.let { Text(text = it) }
Button(onClick = {
viewModel.getToken()
}) {
Text(text = "Update")
}
}
}
}
}
}
}
This is what I have on the PHP side to receive the call and post results.
<?php
if (isset($_SERVER["HTTP_AUTHORIZATION"])) {
echo json_encode(array(
'token' => $_SERVER["HTTP_AUTHORIZATION"] // or type in a message like ---> "Type in your message here"
));
exit;
} else {
echo json_encode(array(
'id' => 1,
'message' => "Problem"
));
}
?>
Leave a Reply