
This is a loading dialog made with Android Jetpack Compose. It uses two APIs – Dialog and CircularProgressIndicator to draw the custom UI on the screen. You can download the source code for free below.
It uses Dialog API to get its functionality like open and close animation, and background overlay color. Next, it creates the custom UI inside the Dialog using the Surface API. You can write anything inside the Surface. I have added CircularProgressIndicator and Text (that shows loading). I put these two inside a Column to arrange them vertically. I added a gap using Spacer.
The loading dialog made with Android Jetpack Compose takes multiple parameters. They are cornerRadius, progressIndicatorColor, progressIndicatorSize, and padding values. You can adjust them as per your requirements.
Coming to the animation, it uses rememberInfiniteTransition API with initial and final values equal to 0f and 360f. The animation duration is 600 ms. It actually rotates the angle of the progress bar. I set the gradient border to it. As the angle is changed from 0f to 360f, the border gets rotated and it looks like a loading bar.
You can open the dialog using the button. The background task is performed by using the view model. I set the 3 seconds delay instead of the task inside viewModelScope.launch block. After 3 seconds, the open variable value becomes false and the dialog gets closed itself.
If you want to use the code, you need to download the latest version of Android Studio. Jetpack Compose doesn’t support in the older versions.
Related:
Final output:
If the video isn’t working, watch it on the YouTube.
Helpful links to understand the code:
- CircularProgressIndicator
- Jetpack Compose Gradient Backgrounds
- Infinite Animation
- Dialog API
- Jetpack Compose with ViewModel
Here are the Gradle files used in the project.
Here is the Source Code:
MainActivity.kt:
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.keyframes
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/*
You can use the following code for commercial purposes with some restrictions.
Read the full license here: https://semicolonspace.com/semicolonspace-license/
For more designs with source code,
visit: https://semicolonspace.com/jetpack-compose-samples/
*/
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BlogPostsTheme(darkTheme = false) {
Column(
modifier = Modifier
.fillMaxSize()
.background(color = Color.White),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
val viewModel: MyViewModel = viewModel()
val openDialog by viewModel.open.observeAsState(false)
ButtonClick(buttonText = "Open Dialog") {
viewModel.open.value = true
}
if (openDialog) {
viewModel.startThread()
DialogBoxLoading()
}
}
}
}
}
}
@Composable
fun DialogBoxLoading(
cornerRadius: Dp = 16.dp,
paddingStart: Dp = 56.dp,
paddingEnd: Dp = 56.dp,
paddingTop: Dp = 32.dp,
paddingBottom: Dp = 32.dp,
progressIndicatorColor: Color = Color(0xFF35898f),
progressIndicatorSize: Dp = 80.dp
) {
Dialog(
onDismissRequest = {
}
) {
Surface(
elevation = 4.dp,
shape = RoundedCornerShape(cornerRadius)
) {
Column(
modifier = Modifier
.padding(start = paddingStart, end = paddingEnd, top = paddingTop),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
ProgressIndicatorLoading(
progressIndicatorSize = progressIndicatorSize,
progressIndicatorColor = progressIndicatorColor
)
// Gap between progress indicator and text
Spacer(modifier = Modifier.height(32.dp))
// Please wait text
Text(
modifier = Modifier
.padding(bottom = paddingBottom),
text = "Please wait...",
style = TextStyle(
color = Color.Black,
fontSize = 16.sp,
fontFamily = FontFamily(
Font(R.font.roboto_regular, FontWeight.Normal)
)
)
)
}
}
}
}
@Composable
fun ProgressIndicatorLoading(progressIndicatorSize: Dp, progressIndicatorColor: Color) {
val infiniteTransition = rememberInfiniteTransition()
val angle by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
animation = keyframes {
durationMillis = 600
}
)
)
CircularProgressIndicator(
progress = 1f,
modifier = Modifier
.size(progressIndicatorSize)
.rotate(angle)
.border(
12.dp,
brush = Brush.sweepGradient(
listOf(
Color.White, // add background color first
progressIndicatorColor.copy(alpha = 0.1f),
progressIndicatorColor
)
),
shape = CircleShape
),
strokeWidth = 1.dp,
color = Color.White // Set background color
)
}
@Composable
fun ButtonClick(
buttonText: String,
onButtonClick: () -> Unit
) {
Button(
shape = RoundedCornerShape(5.dp),
colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colors.primary),
onClick = {
onButtonClick()
}) {
Text(
text = buttonText,
fontSize = 16.sp,
color = Color.White
)
}
}
class MyViewModel : ViewModel() {
// Dialog box
var open = MutableLiveData<Boolean>()
fun startThread() {
viewModelScope.launch {
withContext(Dispatchers.Default) {
// Do the background work here
// I'm adding delay
delay(3000)
}
closeDialog()
}
}
private fun closeDialog() {
open.value = false
}
}