Create ChatGPT App with Jetpack Compose — (Kotlin, Koin, MVVM, DB Room, etc)

HariAgusWidakdo
4 min readSep 2, 2023

--

Bismillah hello everyone, Alhamdulillah, Allah has given me the opportunity to write an article.

On this occasion I tried to write a fairly complex article about ChatGPT with Jetpack Compose which I call this application the Takon(Ask in Javanese Language) Application hehe2x.

The application we make will be more or less like that *NB : Thanks to those the make design

From the design above we can see there are 3 views or pages and several other components

SplashScreen

The first screen is not too complicated only requires 1 of my images, in this case there are many functions in Jetpack Compose that can be used, here I only use Surface

Surface(
modifier = Modifier.fillMaxSize(),
color = BluePrimary
) {
Image(
modifier = Modifier
.padding(horizontal = 32.dp)
.height(262.dp)
.fillMaxWidth(),
painter = painterResource(id = R.drawable.ic_logo),
contentDescription = ""
)
}

OnBoarding

In the second view we can analyze there is a vertically oriented view, so we will use @Coloum for the parent view which is packaged with @Surface.

Surface(
modifier = Modifier.fillMaxSize(),
color = Color.White
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(start = 28.dp, end = 28.dp, top = 80.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Column(
modifier = Modifier.weight(1f),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "You AI Assistant",
color = BluePrimary,
style = MaterialTheme.typography.titleMedium.copy(
fontWeight = FontWeight.Bold,
fontSize = 23.sp
)
)

Spacer(modifier = Modifier.height(14.dp))

Text(
text = "Using this software,you can ask you\n" +
"questions and receive articles using\n" +
"artificial intelligence assistant",
textAlign = TextAlign.Center,
style = MaterialTheme.typography.labelSmall.copy(
fontSize = 15.sp,
color = TextColorGray
)
)

Image(
modifier = Modifier
.height(400.dp)
.padding(top = 84.dp)
.fillMaxWidth(),
painter = painterResource(id = R.drawable.img_on_boarding),
contentDescription = ""
)
}

Button(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 34.dp),
colors = ButtonDefaults.buttonColors(containerColor = BluePrimary),
onClick = {
navController.popBackStack()
navController.navigate(route = Screen.Message.route)
}
) {
Text(
modifier = Modifier.weight(1f),
textAlign = TextAlign.Center,
text = "Continue",
style = MaterialTheme.typography.titleLarge.copy(
fontWeight = FontWeight.Bold,
fontSize = 19.sp
)
)

Image(
modifier = Modifier.size(24.dp),
imageVector = Icons.Default.ArrowForward,
colorFilter = ColorFilter.tint(color = Color.White),
contentDescription = ""
)

}
}
}

MessageScreen

The last display is a message, maybe here it can be said to be complex, because it requires several components.

Scaffold(
containerColor = Color.White,
topBar = {
ToolbarMessageTakon()
},
floatingActionButton = {
WriteMessageCard(
modifier = Modifier.padding(horizontal = 16.dp, vertical = 16.dp),
value = input,
onValueChange = { value ->
setInput(value)
},
onClickSend = {
if (input.isNotEmpty()) {
viewModel.askQuestion(question = input)
setInput("")
}
},
)
},
floatingActionButtonPosition = FabPosition.Center
) { paddingValues ->
Column(
modifier = Modifier
.padding(paddingValues = paddingValues)
.fillMaxSize()
) {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(top = 8.dp),
verticalArrangement = Arrangement.spacedBy(space = 8.dp),
horizontalAlignment = Alignment.End
) {
items(messages) { message ->
if (message.fromUser) {
MessengerItemCard(
modifier = Modifier.align(Alignment.End),
message = message.content
)
} else {
ReceiverMessageItemCard(message = message.content)
}
}
}
}
}

For the components needed, you can check the github link which I will include later.

Ok then we will connect it with the API from ChatGPT, here using Dependecy Injection KOIN from Kotlin, for detail KOIN you can check in the link : https://insert-koin.io/

startKoin {
modules(module {
single {
Retrofit.Builder()
.baseUrl("https://api.openai.com/v1/chat/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
single {
val retrofit: Retrofit = get()
retrofit.create(Api::class.java)
}
single {
Room.databaseBuilder(
this@TakonApp,
AppDatabase::class.java,
"db_takon"
).build()
}
single {
val api: Api = get()
val database: AppDatabase = get()

RepositoryImpl(api = api, dao = database.answerDao())

} bind Repository::class
})
}

Next we will create a viewModel that serves as a bridge between Repository and View

class TakonViewModel : ViewModel(), KoinComponent {

private val database: AppDatabase by inject()
private val repository: Repository by inject()

private val _messages: MutableStateFlow<List<Message>> = MutableStateFlow(emptyList())
val messages = _messages.asStateFlow()

private val _loading = MutableStateFlow(false)
val loading = _loading.asStateFlow()

init {

viewModelScope.launch {
repository.getMessages().collect { data ->
_messages.update { data }
}
}

}

fun askQuestion(question: String) {
viewModelScope.launch {
withContext(Dispatchers.IO) {
database.answerDao().addAnswer(
answerEntity = AnswerEntity(
role = "user",
content = question
)
)
}
_loading.update { true }
repository.askQuestion(
prevQuestion = messages.value,
question = question
).also { baseModel ->
_loading.update { false }
when (baseModel) {
is BaseModel.Success -> {
withContext(Dispatchers.IO) {
database.answerDao().addAnswer(answerEntity = AnswerEntity(
role = "assistant",
content = baseModel.data.choices.first().message.content
)
)
}
}

is BaseModel.Error -> {
println("Something wrong : ${baseModel.error}")
}

else -> {}
}
}
}
}

}

The last step is to send messages and receive messages in the View section, which I have actually written above in the MessageScreen section.

To hold messages :

val (input, setInput) = remember { mutableStateOf("") }

Sends a message to the server :

if (input.isNotEmpty()) {
viewModel.askQuestion(question = input)
setInput("")
}

Display the list of conversations :

LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(top = 8.dp),
verticalArrangement = Arrangement.spacedBy(space = 8.dp),
horizontalAlignment = Alignment.End
) {
items(messages) { message ->
if (message.fromUser) {
MessengerItemCard(
modifier = Modifier.align(Alignment.End),
message = message.content
)
} else {
ReceiverMessageItemCard(message = message.content)
}
}
}

OK, finally finished, we have created a ChatGPT application using Jetpack Compose and several other technologies, I hope this article can be useful. For the github link, you can check below.

https://github.com/HariAgus/TakonApp-Comppose/tree/master

--

--

HariAgusWidakdo

Mobile Developer | Kotlin | SwiftUI 📱. Sometimes write my story 📚