Best Website for Jetpack Compose App Development

Android Jetpack Compose

Stay ahead with the latest tools, trends, and best practices in Android Jetpack Compose App development

Build a Modern Cricket Score App in Jetpack Compose

Build a Modern Cricket Score App in Jetpack Compose - Responsive Blogger Template
Build a Modern Cricket Score App in Jetpack Compose

๐Ÿ Build a Modern Cricket Score App

✨ Introduction

If you love both cricket and Android development, this tutorial is the perfect match! In this post, we’ll build a modern live cricket score app using Jetpack Compose and CricAPI — a free and easy-to-use API that provides real-time cricket data. The best part? We’ll do it without OkHttp, making the setup beginner-friendly and efficient.

๐Ÿงฐ What You’ll Learn

  • Connect Jetpack Compose app with a real cricket API
  • Fetch live data such as teams, scores, and venues
  • Build beautiful UI components with Compose
  • Implement navigation and a details screen

⚙️ Step 1: Project Setup

Create a new Jetpack Compose project in Android Studio (Arctic Fox or newer).

Add dependencies in build.gradle


implementation("androidx.navigation:navigation-compose:2.8.0")
implementation("io.coil-kt:coil-compose:2.4.0")
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
    

CodingBihar

— Explore Expert-Written Tutorials, Projects, and Tools to Master App Development —

๐ŸŒ Step 2: Fetch Data from CricAPI

Use this endpoint to get live match data:

https://api.cricapi.com/v1/currentMatches?apikey=YOUR_API_KEY&offset=0

Data classes for API response


data class MatchResponse(val data: List<Match>)
data class Match(
    val id: String,
    val name: String,
    val status: String,
    val venue: String,
    val teamInfo: List<TeamInfo>,
    val score: List<Score>?
)
data class TeamInfo(val name: String, val shortname: String, val img: String)
data class Score(val r: Int, val w: Int, val o: Float, val inning: String)
    

๐Ÿš€ Step 3: Create a Retrofit API Service


interface CricketApiService {
    @GET("v1/currentMatches")
    suspend fun getMatches(
        @Query("apikey") apiKey: String,
        @Query("offset") offset: Int = 0
    ): MatchResponse
}
    

๐ŸŽจ Step 4: Build the UI with Jetpack Compose


@Composable
fun MatchCard(match: Match, onClick: () -> Unit) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(8.dp)
            .clickable { onClick() },
        shape = RoundedCornerShape(12.dp),
        elevation = CardDefaults.cardElevation(4.dp)
    ) {
        Column(Modifier.padding(12.dp)) {
            Row(
                modifier = Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.SpaceBetween
            ) {
                match.teamInfo.forEach { team ->
                    Column(horizontalAlignment = Alignment.CenterHorizontally) {
                        Image(
                            painter = rememberAsyncImagePainter(team.img),
                            contentDescription = null,
                            modifier = Modifier.size(48.dp)
                        )
                        Text(team.shortname, fontWeight = FontWeight.Bold)
                    }
                }
            }
            Spacer(modifier = Modifier.height(6.dp))
            Text(text = match.name, fontWeight = FontWeight.SemiBold)
            Text(text = match.status, style = MaterialTheme.typography.bodySmall)
            match.score?.forEach {
                Text("${it.inning}: ${it.r}/${it.w} (${it.o} overs)")
            }
        }
    }
}
    

๐Ÿ“ฑ Step 5: Add a Details Screen


@Composable
fun MatchDetailsScreen(match: Match) {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(match.name, fontWeight = FontWeight.Bold, fontSize = 22.sp)
        Spacer(modifier = Modifier.height(8.dp))
        Text("Venue: ${match.venue}")
        Text("Status: ${match.status}")
        Spacer(modifier = Modifier.height(12.dp))
        match.score?.forEach {
            Text("${it.inning} — ${it.r}/${it.w} in ${it.o} overs")
        }
    }
}
    

๐ŸŒ Step 6: Optional — Show Data in Hindi

CricAPI data is in English, but you can translate your app’s UI labels using stringResource() and Hindi strings.xml.


<string name="venue">เคฎैเคฆाเคจ</string>
<string name="status">เคธ्เคฅिเคคि</string>
    

Then use it in Compose:


Text(text = stringResource(R.string.venue) + ": " + match.venue)
    

How I built My Final Code is Below

MainActivity

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            EQPlayerTheme {
                val context = LocalContext.current
                /*Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Greeting(
                        name = "Android",
                        modifier = Modifier.padding(innerPadding)
                    )
                }*/
                
                CricketAppNav()


            }
        }
    }
}

@Composable
fun CricketAppNav(viewModel: MatchViewModel = androidx.lifecycle.viewmodel.compose.viewModel()) {
    val navController = rememberNavController()

    NavHost(navController = navController, startDestination = "match_list") {

        // ๐Ÿ List Screen
        composable("match_list") {
            MatchListScreen(
                viewModel = viewModel,
                onMatchClick = { match ->
                    val matchJson = java.net.URLEncoder.encode(Gson().toJson(match), "UTF-8")
                    navController.navigate("match_detail/$matchJson")
                }
            )
        }

        // ๐Ÿ“„ Details Screen
        composable(
            "match_detail/{match}",
            arguments = listOf(navArgument("match") { type = NavType.StringType })
        ) { backStackEntry ->
            val matchJson = backStackEntry.arguments?.getString("match") ?: ""
            val match = Gson().fromJson(
                java.net.URLDecoder.decode(matchJson, "UTF-8"),
                Match::class.java
            )
            MatchDetailsScreen(match = match, onBack = { navController.popBackStack() })
        }
    }
}
RetrofitApi
package com.codingbihar.cricketbuzz

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
import retrofit2.http.Query


data class MatchResponse(
    val data: List?,
    val status: String?
)

data class Match(
    val id: String,
    val name: String,
    val matchType: String,
    val status: String,
    val venue: String,
    val date: String,
    val teams: List,
    val teamInfo: List,
    val score: List?
)

data class TeamInfo(
    val name: String,
    val shortname: String,
    val img: String
)

data class Score(
    val r: Int,
    val w: Int,
    val o: Float,
    val inning: String
)

interface CricApiService {

    @GET("v1/currentMatches")
    suspend fun getCurrentMatches(
        @Query("apikey") apiKey: String,
        @Query("offset") offset: Int = 0
    ): MatchResponse

    companion object {
        private const val BASE_URL = "https://api.cricapi.com/"

        fun create(): CricApiService {
            return Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build()
                .create(CricApiService::class.java)
        }
    }
}
MatchViewModel
package com.codingbihar.cricketbuzz

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch


class MatchViewModel : ViewModel() {

    private val _matches = MutableStateFlow>(emptyList())
    val matches: StateFlow> = _matches

    private val api = CricApiService.create()

    fun fetchMatches() {
        viewModelScope.launch {
            try {
                val response = api.getCurrentMatches("01d061b5-3563-42f5-b42f-0e3ghg143814")
                _matches.value = response.data ?: emptyList()
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }
}
CricketScreen
package com.codingbihar.cricketbuzz

import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import coil.compose.rememberAsyncImagePainter

@Composable
fun MatchListScreen(
    viewModel: MatchViewModel,
    onMatchClick: (Match) -> Unit
) {
    Column(Modifier.fillMaxSize().systemBarsPadding()) {
        val matches by viewModel.matches.collectAsState()

        LaunchedEffect(Unit) {
            viewModel.fetchMatches()
        }

        Row (Modifier.fillMaxWidth()){
            Text("\uD83C\uDFCF", style = MaterialTheme.typography.displayMedium)
            Text(text = stringResource(R.string.app_name), style = MaterialTheme.typography.displayMedium)
        }

        LazyColumn(
            modifier = Modifier.padding(8.dp)
        ) {
            items(matches) { match ->
                MatchCard(match = match, onClick = { onMatchClick(match) })
            }
        }

    }
}

@Composable
fun MatchCard(
    match: Match,
    onClick: (Match) -> Unit
) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(vertical = 6.dp)
            .clickable { onClick(match) },
        shape = RoundedCornerShape(12.dp),
        elevation = CardDefaults.cardElevation(4.dp)
    ) {
        Column(modifier = Modifier.padding(12.dp)) {
            Row(
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.SpaceBetween,
                modifier = Modifier.fillMaxWidth()
            ) {
                match.teamInfo.forEach { team ->
                    Column(horizontalAlignment = Alignment.CenterHorizontally) {
                        Image(
                            painter = rememberAsyncImagePainter(team.img),
                            contentDescription = team.name,
                            modifier = Modifier.size(48.dp),
                            contentScale = ContentScale.Crop
                        )
                        Text(team.shortname, fontWeight = FontWeight.Bold)
                    }
                }
            }

            Spacer(modifier = Modifier.height(8.dp))
            Text(text = match.name, fontWeight = FontWeight.SemiBold)
            Text(text = match.status, style = MaterialTheme.typography.bodySmall)
            Text(text = "Venue: ${match.venue}", style = MaterialTheme.typography.bodySmall)
            match.score?.forEach {
                Text("${it.inning}: ${it.r}/${it.w} (${it.o} overs)")
            }
        }
    }
}

@Composable
fun MatchDetailsScreen(
    match: Match,
    onBack: () -> Unit
) {
    Column(
        modifier = Modifier.systemBarsPadding()
            .padding(16.dp)
    ) {
        Row(
            modifier = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.SpaceEvenly
        ) {
            match.teamInfo.forEach { team ->
                Column(horizontalAlignment = Alignment.CenterHorizontally) {
                    Image(
                        painter = rememberAsyncImagePainter(team.img),
                        contentDescription = team.name,
                        modifier = Modifier.size(72.dp)
                    )
                    Text(team.name, fontWeight = FontWeight.Bold)
                }
            }
        }

        Spacer(modifier = Modifier.height(12.dp))
        Text("๐ŸŸ Venue: ${match.venue}", style = MaterialTheme.typography.bodyMedium)
        Text("๐Ÿ“… Date: ${match.date}", style = MaterialTheme.typography.bodyMedium)
        Text("⚡ Status: ${match.status}", style = MaterialTheme.typography.bodyMedium)

        Spacer(modifier = Modifier.height(16.dp))
        HorizontalDivider()
        Text(
            "๐Ÿ“Š Scorecard",
            style = MaterialTheme.typography.titleMedium,
            fontWeight = FontWeight.SemiBold,
            modifier = Modifier.padding(vertical = 8.dp)
        )
        match.score?.forEach { score ->
            Card(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(vertical = 4.dp),
                shape = RoundedCornerShape(10.dp)
            ) {
                Column(modifier = Modifier.padding(12.dp)) {
                    Text(text = score.inning, fontWeight = FontWeight.SemiBold)
                    Text(text = "Runs: ${score.r}, Wickets: ${score.w}, Overs: ${score.o}")
                }
            }
        }
    }
}

Output:

Modern Cricket Score App in Jetpack Compose Screenshot

๐Ÿ Conclusion

Congratulations ๐ŸŽ‰! You’ve just built a live cricket score app with Jetpack Compose and CricAPI — clean, fast, and modern. Try adding player details, Hindi toggle, or live refresh to enhance your project further.

๐Ÿ’ฌ FAQs

Q1. Is CricAPI free?

Yes, it offers a free plan with limited daily API calls.

Q2. Can I use Hindi or other languages?

You can translate UI text; the API data remains in English.

Q3. How can I auto-refresh the scores?

Use LaunchedEffect with delay() in Compose to refetch every 30 seconds.

SkillDedication

— Python High Level Programming Language- Expert-Written Tutorials, Projects, and Tools—

Special Message

Welcome To Coding Bihar๐Ÿ‘จ‍๐Ÿซ