Create ChatGPT App with Jetpack Compose — (Kotlin, Koin, MVVM, DB Room, etc)
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.
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.