MVVM Android 和 ChatGPT 集成。

嗨,开发者们!

今天我们将使用MVVM架构和ChatGPT集成构建一个Android Jetpack Compose应用程序。这将是一个简单的应用程序,我们将创建一个随机文本生成应用程序。

首先,我将在Android Studio上创建项目。让我们使用Jetpack Compose创建一个空白活动,并为应用程序创建一个名称。

Look at symbol on the image

好的,现在让我们创建我们的包来组织我们的MVVM架构。所有的包都来自我们的主包,即com.example.yourappname。在下面的图片中,我们可以看到如何创建一个包,所以在这里我们将创建一个Model、一个View和一个ViewModel。

首先让我们来看一下我们的ui.theme包,在项目启动时它会自动创建。在这个包内有三个文件,Color(颜色)、Theme(主题)和Type(类型),所以我们将自定义Color和Theme文件。定义我们的颜色,并将它们添加到我们的主题中。

让我们在Color文件中创建两种颜色。

val background = Color(red = 40, green = 225, blue = 222, alpha = 0xFF)
val purple = Color(red = 135, green = 40, blue = 225, alpha = 0xFF)

我们将把它们添加到主题文件中,并记住我们可以用我们的颜色替换自动生成的颜色。

private val DarkColorScheme = darkColorScheme(
primary = background

)

private val LightColorScheme = lightColorScheme(
primary = background
)

@Composable
fun TalkEnglishTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}

darkTheme -> DarkColorScheme
else -> LightColorScheme
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = colorScheme.primary.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
}
}

MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

现在让我们来构建我们的代码结构。我们从 Views 开始,我们将使用两个视图来了解在 Android 上使用 Jetpack Compose 进行屏幕转换。

第一个视图将被称为StartView。

@Composable
fun StartView(viewModel: MainViewModel) {
Box(
modifier = Modifier
.fillMaxSize()
.background(background),
contentAlignment = Alignment.Center
) {
Button(
onClick = { viewModel.navigateTo("GameView") },
modifier = Modifier.size(width = 200.dp, height = 50.dp),
colors = ButtonDefaults.buttonColors(purple)
) {
Text("Start")
}
}
}

@Preview(showBackground = true)
@Composable
fun PreviewStartView() {
val viewModel = MainViewModel()
StartView(viewModel)
}

我们的第一个视图是一个简单的屏幕,中间只有一个按钮,它将带我们到第二个屏幕。你可以看到我们使用了我们的颜色、背景和紫色。

@Composable
fun GameView(viewModel: MainViewModel) {
val sentText = viewModel.generatedText.value
Box(
modifier = Modifier
.fillMaxSize()
.background(background),
contentAlignment = Alignment.Center
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.padding(16.dp)
) {
Text(
text = sentText,
modifier = Modifier.padding(bottom = 15.dp)
)
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
Button(onClick = { viewModel.generatedNewText() },
modifier = Modifier.size(width = 160.dp, height = 50.dp),
colors = ButtonDefaults.buttonColors(purple)
) {
Text(text = "NEW")
}
}
}
}
}

@Preview(showBackground = true)
@Composable
fun PreviewGameView() {
val viewModel = MainViewModel()
GameView(viewModel)
}

在这两个视图中,创建了一个ViewModel的实例,因为ViewModel将管理屏幕的追踪和chatGPT在我们的应用程序中的集成,所以我们将最后处理ViewModel。视图结构遵循可组合的模式,第二个名为GameView的视图有一个包含两个更多组件的Column,即Text和Row,最后Row接收一个Button组件,我们将用它来生成随机文本。

让我们先创建模型,在这个文件中,我们只需要放入我们将在Chat-GPT请求和响应中使用的内容。在请求中,我们需要发送提示、令牌数量和模型,这里我们将使用davinci-002。

data class RequestBody(
val prompt: String,
val max_tokens: Int,
val model: String
)

data class OpenAIResponse(
val choices: List<Choice>
)

data class Choice(
val text: String
)

现在让我们创建我们的服务文件,这是一个接口文件,您可以创建新的>Kotlin类/文件,并在那里选择一个接口文件。所以这里我称这个文件为OpenAiService。

interface OpenAiService {
@Headers("Content-Type: application/json")
@POST("/v1/completions")
suspend fun createCompletion(@Header("Authorization") apiKey: String, @Body body: com.example.talkenglish.Model.RequestBody): Response<OpenAIResponse>
}

在这个接口文件中,我们将获取我们的标头和使用的端点。请注意,我传递了@POST,因为我们将向openAI服务发送数据,并创建了一个具有授权头和类型为字符串的apiKey参数的函数。

apiKey参数是在openAI网站上创建的密钥,授权标头用于将该密钥作为承载令牌发送。之后有一个主体,该主体是我们在模型文件中创建的请求主体,其中包括提示文本、最大令牌数量和模型。

class MainViewModel: ViewModel() {
private val _currentScreen = MutableStateFlow("StartView")
val currentScreen: StateFlow<String> = _currentScreen
private val _generatedText = mutableStateOf("Initial Text")
val generatedText: State<String> = _generatedText

fun navigateTo(screen: String) {
viewModelScope.launch {
_currentScreen.value = screen
}
}

private val okHttpClient = OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
.addInterceptor { chain ->
val request = chain.request().newBuilder()
.header("Authorization", "Bearer ${ ApiKey.OPENAI_API_KEY }")
.build()
chain.proceed(request)
}
.build()


private val openAIApi = Retrofit.Builder()
.baseUrl("https://api.openai.com/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(OpenAiService::class.java)

fun generatedNewText() {
viewModelScope.launch {
//first the prompt
val prompt = "Say a english sentence."

//second the response
val response = openAIApi.createCompletion(
"Bearer ${ApiKey.OPENAI_API_KEY}",
RequestBody(prompt, 50, "davinci-002")
)
if (response.isSuccessful && response.body() != null) {
_generatedText.value = response.body()!!.choices.first().text.trim()
} else {
Log.e("MainViewModel", "Falha na requisição: ${response.errorBody()?.string()}")
}
}
}
}

这是ViewModel,我们将在这里管理整个应用程序。首先有一些私有变量,用于管理我们屏幕的状态并存储由AI生成的文本。navigateTo函数用于在不同屏幕之间进行过渡,就这些。

我们创建了一个okHttpClient的结构,其中我们以承载令牌的方式获取了我们的密钥,我们将在retrofit中使用这个客户端。

从OpenAI获得baseUrl以及okHttpClient和我们在create中提供的openAIService的retrofit。

最后,我们创建了generatedNewText()函数,在该函数中,我们创建了固定的提示和一个接收openAIApi.createCompletion的回应,从这里我们获得了令牌和请求的正文。然后,我们只需通过一个检查,将GameView屏幕上的Text组件替换为由人工智能生成的新文本。

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
TalkEnglishTheme {
val viewModel: MainViewModel = viewModel()
AppScreen(viewModel)
}
}
}
}

@Composable
fun AppScreen(viewModel: MainViewModel) {
val currentScreen = viewModel.currentScreen.collectAsState().value

when(currentScreen){
"StartView" -> StartView(viewModel)
"GameView" -> GameView(viewModel)
}

}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
TalkEnglishTheme {
val viewModel = MainViewModel()
AppScreen(viewModel = viewModel)
}
}

最后,我们的MainActivity将接收我们的两个屏幕,StartView和GameView,记住管理由ViewModel完成。

我将在下面发布一些应用程序截图,享受这个过程,创造你想要的东西。这只是一个想法,你可以改变和创造很多东西。

谢谢,让我们开始编码吧!

2024-02-03 04:16:13 AI中文站翻译自原文