Недавно Google анонсировала Android Studio Bot — нового чат‑помощника для нативной андроид разработки. Кроме генерации ответов на вопросы он обладает дополнительным набором фичей, предназначеннных на порядок облегчить работу разработчику. В этой статье мы разберём, на что способен новый инструмент от Google, сравним его с нашумевшим ChatGPT и выясним, сделает ли он вас эффективнее при решении повседневных задач.

Что такое Studio Bot
Как можно присоединиться к тесту?
Особенности Studio Bot
Формат беседы
Собеседуем Studio Bot
Вывод

Что такое Studio Bot

Android Studio Bot – это интегрированный в среду разработки ИИ чат-помощник, основное призвание которого - увеличить продуктивность, отвечая на интересующие запросы, “не отходя от кассы”. Его основной функционал содержит генерацию кода, поиск необходимых ресурсов и предоставление лучших практик разработки от Google. 

Любой введённый вопрос отправляется на сервера Google, там обрабатывается и возвращает готовый ответ.

Сейчас Studio Bot всё ещё находится в разработке, поэтому может предоставлять неверные и/или неточные ответы, и открыт для тестирования. Предлагаю установить бот и ознакомиться с ним на практике.

Как можно присоединиться к тесту?

1) скачать последнюю версию Android Studio Hedgehog

2) быть жителем Соединенных Штатов или включить соответствующий VPN, так как бот доступен только там;

3) открыть Android Studio. Studio Bot должен быть на стенке справа. Если нет, то его можно включить, пройдя по следующему пути: View > Tool Windows > Studio Bot;

4) залогиниться в аккаунте Google через бота (теперь нам и нужен VPN) и принять пользовательское соглашение;

5) Бот к вашим услугам.

Особенности Studio Bot

Прежде чем перейти к сравнению, рассмотрим особенности работы бота и его дополнительный функционал, потому что мы будем ссылаться на них в дальнейшем.

Каждый сгенерированный участок кода можно:

  1. Скопировать.

  2. Вставить на место курсора в текущем файле.

  3. Создать новый файл и поместить фрагмент там. В таком случае, если в коде есть какой-то класс, то название файла будет идентичное. Если функции или файл с таким названием в пакете уже существуют, то бот предложить задать название самостоятельно.

  4. Открыть в Playground (скриншот ниже) для тестирования (работает только с Kotlin кодом).

Все эти функции продемонстрированы на скриншоте ниже, в верхнем правом углу слева направо в соответствующем порядке:

Studio Bot Playground:

Довольно удобная вещь, можно проводить отладку функций, не запуская приложение. Результаты выводятся на консоль справа.

Помимо прочего Studio Bot может предоставлять ссылки на ресурсы или на документацию от Google. Я не случайно их разделил, разницу вы можете увидеть на снимке ниже:

Сторонние ресурсы в виде маркированного списка:

Официальная документация в виде кнопки перехода:

А также напрямую внедрять зависимости в build.gradle (.kts):

И разрешения в AndroidManifest.xml:

Кроме этого можно задавать вопрос по выделенному участку кода с помощью контекстного меню. Код будет вставлен в форму сообщения боту с заголовком “Explain the following code:”. Горячей клавиши для этого действия пока нет (или я не нашёл, где поставить). Также стоит обратить внимание, что отправленный фрагмент используется только для генерации ответа, а не обучения нейросети.

Из неудобного - копирование сгенерированного текста (а я этим занимался много) также доступно только с помощью контекстного меню и работает для каждого абзаца отдельно. То есть, если вам захочется перейти по любой из предоставленных ссылок на сторонние ресурсы, придётся скопировать весь абзац и удалять ненужные.

P.S. Предлагаю оценить, как бот отвечает на запрос на русском:

В целом это все доступные функции Studio Bot. Перейдём к тестированию.

Формат беседы

Тест будем проводить аналогично собеседованию: сначала зададим несколько вопросов по языку Kotlin, а затем по системе Android. После этого пойдёт практическая часть, которая будет включать генерацию функции по заданным условиям, объяснение логики фрагмента кода (Ask Studio Bot) и написание простого приложения заметок.

В основе оценивания теории лежит следующий принцип: ответы должны заменять нам обычный поиск информации в интернете или на официальных сайтах. Эталоном сравнения выступает официальная документация Kotlin и Android.

В статье не будет приведена вся переписка с обеими платформами, так как это займёт много места. По желанию прочитать полную версию можно здесь, а в тексте будут отражены необходимые фрагменты.

Также отмечу, что больше внимания будет уделено именно Studio Bot (далее – бот), потому что возможности ChatGPT (далее – чат) многие успели опробовать на практике, а кто не успел - весьма о них наслышан. 

Собеседуем Studio Bot

Первый вопрос.

Q1: What is a data class in Kotlin?

Studio Bot: Sure, a data class in Kotlin is a class that is used to represent a data structure. It is a class that has a primary constructor that takes arguments for all of the properties of the class, and it has a toString(), equals(), and hashCode() method defined for it. Data classes are often used to represent objects that are immutable, meaning that they cannot be changed once they are created.

// Studio Bot
data class Person(
    val name: String, 
    val age: Int
)

Первый ответ немного разочаровал: бот предоставил определение, упомянул переопределённые методы, привёл пример, но при этом упустил такие уникальные черты, как метод copy(), компонент функции, которые помогают использовать мульти-декларации (destructuring declarations). У ChatGPT таких проблем не возникло, сам пример был идентичный.

Второй вопрос о том, как Kotlin обеспечивает операции с нулевым значением:

Q2: How does Kotlin provide null-safety?

Studio Bot: Sure. Kotlin provides null-safety by using the ? operator. When you declare a variable or property with the ? operator, it means that the variable or property can be null. For example:

// Studio Bot
val name: String? = null

You can check for nullity using the !! operator. For example:

// Studio Bot
val lastName = name!!.toUpperCase()

You can also use the ?. operator to access the properties of a null object. For example:

// Studio Bot
val name: String? = null
val name: String? = null
val lastName = name!!.toUpperCase()
val name: String? = null
val lastName = name?.toUpperCase()

Мне сразу не понравилось, что он пропустил Elvis-оператор, и я решил задать уточняющий вопрос, после этого он выдал мне те же способы, но уже включая Elvis, скоуп функцию let() и несуществующий метод nullCheck().

Q2a: Are there other ways to handle nullable types in Kotlin?

Studio Bot: Finally, you can use the nullCheck() function to check if a value is null. For example, the following code will print "The value is not null" if the name object is not null, and "The value is null" if it is null:

// Studio Bot
val name: String? = null
println(name.nullCheck()) }

После очередного уточнения бот признал ошибку и извинился.

Q2b: nullCheck() function does not exist

Studio Bot: You are correct, the nullCheck() function does not exist. I apologize for the error. Here is the corrected code:

// Studio Bot
val name: String? = null
println(if (name != null) "The value is not null" else "The value is null")

В целом с приведёнными примерами картина складывается, но есть несколько замечаний: ни один оператор не назван; не все операторы были представлены сразу, их не так чтобы много и ими вопрос не исчерпывается; дан неверный метод, который может спутать незнающего, заставить лишний раз тратить время на поиск. В свою очередь ChatGPT дал полный ответ текстом, без примеров (видимо, из-за постановки вопроса), и их пришлось запрашивать отдельно.

Дальше спросим за inline функции и чем они полезны.

Q3: Explain what is an inline function and why one should use it

Studio Bot: Sure. An inline function in Kotlin is a function that is expanded at the call site instead of being compiled into a separate function. This can be useful for performance reasons, as it can avoid the overhead of function calls. Additionally, inline functions can be used to implement some functional programming concepts, such as higher-order functions and lambda expressions.

Here is an example of an inline function:

// Studio Bot
inline fun myFunction(x: Int, y: Int): Int {
  return x + y
}

This function can be called as follows:

// Studio Bot
val result = myFunction(1, 2)

At compile time, the compiler will expand the myFunction function into the following code:

// Studio Bot
val result = 1 + 2

По большому счёту, первое впечатление о работе inline функций сложится правильное. Но пример я считаю неудачным, так как их использование эффективнее, когда среди параметров присутствует другая функция.

Также бот выделил четыре преимущества и привёл пример к каждому из них: первые два – улучшение производительности и внедрение функционального программирования более-менее соответствует действительности, приводить пример здесь не буду. Вторые же два крайне странные, потому что относятся к любой функции в принципе.

Studio Bot:

To reduce the size of your code: You can use an inline function to reduce the size of your code. For example, the following code defines a function that takes an Int and returns an Int:

// Studio Bot
fun myFunction(x: Int): Int {
  return x + 1
}

Studio Bot: However, the following code defines an inline function that does the same thing, but in a shorter and more concise way:

// Studio Bot
inline fun myFunction(x: Int) = x + 1

Studio Bot:

To make your code more readable: You can use an inline function to make your code more readable. For example, the following code defines a function that takes an Int and returns an Int:

// Studio Bot
fun myFunction(x: Int): Int {
  if (x < 0) {
    return -x
  } else {
    return x
  }
}

Studio Bot: However, the following code defines an inline function that does the same thing, but in a more readable way:

// Studio Bot
inline fun myFunction(x: Int) = if (x < 0) -x else x

Далее по плану вопрос по корутинам. Корутины – это довольно обширная тема, поэтому моим ожиданием было увидеть самую базовую информацию, кратко объясняющую, что это такое, и соотвествующий пример использования.

Q4: What are kotlin coroutines? Give examples.

Оба Studio Bot и ChatGPT привели определение, но в качестве примеров бот предоставил нечто странное: функция launch() перестала быть функцией расширения. Также хотелось бы увидеть применение модификатора suspend.

// Studio Bot

fun main(args: Array<String>) {
  // Create a coroutine that prints the numbers from 1 to 10
  val coroutine = launch {
    for (i in 1..10) {
      // Print the current number
      print(i)
    }
  }

  // Wait for the coroutine to finish
  coroutine.join()
}

ChatGPT справился с запросом лучше: отсутствуют технические ошибки, можем увидеть разницу между launch() и async(), и даны примеры практического использования – сетевой запрос и обработка данных.

// ChatGPT

import kotlinx.coroutines.*

fun main() {
    // Define a coroutine
    val coroutine = GlobalScope.launch {
        // Perform asynchronous tasks
        val result1 = async { fetchDataFromNetwork() }
        val result2 = async { processData(result1.await()) }
        displayResult(result2.await())
    }

    // Main thread continues its execution
    println("Coroutine is performing tasks asynchronously...")

    // Wait for the coroutine to complete
    runBlocking {
        coroutine.join()
    }

    println("Coroutine completed")
}

suspend fun fetchDataFromNetwork(): String {
    delay(2000) // Simulate network request delay
    return "Data from network"
}

suspend fun processData(data: String): String {
    delay(1000) // Simulate processing delay
    return "Processed $data"
}

fun displayResult(result: String) {
    println("Result: $result")
}

В настоящем проекте мы, конечно, не будем использовать GlobalScope и runBlocking(), для демонстрации этого вполне достаточно.

Поговорим о дженериках.

Q5: Explain what are generics in Kotlin and how to use it

Studio Bot: Sure. Generics are a way to specify a type parameter for a function or class. This allows you to write code that can work with any type of data, as long as it meets the requirements of the type parameter.

// Studio Bot

fun <T> sum(numbers: List<T>): T {
  var sum = 0
  for (number in numbers) {
    sum += number
  }
  return sum
}

Studio Bot: This function works with any type of list, as long as the type of the elements in the list is the same as the type parameter T. For example, you could use this function to sum a list of integers, a list of doubles, or a list of any other type of data.

Here's an example of how you would use the sum function with a list of integers:

// Studio Bot

val numbers = listOf(1, 2, 3, 4, 5)
val sum = sum(numbers)
println(sum) // Prints 15

В целом ответ лаконичный и понятный, способный создать первое впечатление о дженериках. 

Ответ чата мне понравился больше: он начал с использования generics в классах, а затем уже в функциях. Хотя оба ничего не сказали о возможностях ограничить указываемые типы и вариантности.

ChatGPT: Generics in Kotlin allow you to write reusable code that can work with different types of data while providing type safety. They provide a way to define classes, interfaces, and functions that can be parameterized with different types. This enables you to create flexible and type-safe abstractions.

To define a generic class or function in Kotlin, you use angle brackets (< >) after the name to specify one or more type parameters. These type parameters represent a placeholder for the actual types that will be used when the class or function is used.

Here's an example of a generic class in Kotlin:

// ChatGPT
class Box<T>(val item: T) {
    fun getItem(): T {
        return item
    }
}

To create an instance of the Box class, you provide the actual type argument within angle brackets:

// ChatGPT
val box = Box<String>("Hello")

In this case, we've specified that T should be replaced with String, so the type of box is Box<String>. The constructor argument "Hello" is of type String, which matches the type parameter T.

Generic functions work similarly. Here's an example:

// ChatGPT
fun <T> printItem(item: T) {
    println(item)
}

Достаточно, с теорией по Kotlin закончили, перейдём к Android, тем более это бот от Google, и предполагается, что он будет лучше ориентироваться в этих вопросах. Итак, первый пошёл.

Q6: What is AndroidManifest.xml?

Studio Bot: Sure, I can help you with that.

AndroidManifest.xml

is an XML file that is used to declare the components of an Android application. It is the first file that is read by the Android system when an application is installed, and it is used to determine what the application does, what permissions it needs, and how it should be displayed on the device.

The AndroidManifest.xml file contains the following elements:

Application: This element declares the main application class for the application.

Activity: This element declares an activity, which is a screen that the user can interact with.

Service: This element declares a service, which is a background process that can run even when the user is not interacting with the application.

BroadcastReceiver: This element declares a broadcast receiver, which is a component that can receive notifications from the system.

ContentProvider: This element declares a content provider, which is a component that provides access to data.

Ответ вполне удовлетворительный. Далее бот предлагает обратиться к официальной документации, если нам интересно узнать больше, хотя ссылку не предоставляет, несмотря на заложенную возможности это делать.

Далее он приводит пример файла, который у меня почему-то выглядит так:

Studio Bot: Here is an example of an AndroidManifest.xml file:

<blocked>

Я пробовал пару раз переспросить, он отвечал абсолютно идентично.

Также мы впервые за использование получили ссылки на ресурсы, на которые, как говорилось ранее, нельзя перейти по нажатию:

Studio Bot: 

References:

* https://stackoverflow.com/q/33191494

* https://androiderrors.com/how-to-allow-all-network-connection-types-http-and-https-in-android-9-pie/

И я был немного удивлен, что эти переходы явно не на официальные сайты, и решил их проверить.

Первый, хоть косвенно, но касался манифеста.

Второй у меня и вовсе не открылся.

ChatGPT добавил, что в файле могут указываться разрешения и интент фильтры. В целом информация идентичная.

ChatGPT: 

Here are some key elements and attributes found in the AndroidManifest.xml file:

1. Package: It specifies the unique identifier for the application package. It is typically in the form of "com.example.myapp."

2. Application: This element defines the application's properties and components. It includes attributes like the application name, theme, icon, and more.

3. Activities: Activities represent the different screens or user interfaces within an application. Each activity is declared as a separate element within the manifest file.

4. Services: Services are background processes that perform tasks without a user interface. Examples include music playback, network operations, or data synchronization.

5. Broadcast Receivers: Broadcast receivers respond to system-wide events or broadcasts. They are used to listen for and react to specific events like the device booting up or the battery being low.

6. Content Providers: Content providers manage access to a structured set of data. They allow other applications to query or modify the data they hold.

7. Permissions: The manifest file declares the permissions required by the application, such as accessing the internet, reading contacts, or using the camera. These permissions must be explicitly requested and granted by the user during installation or runtime.

8. Intent Filters: Intent filters define the types of intents an activity, service, or broadcast receiver can respond to. They specify the actions, categories, and data types that an application can handle.

Далее я решил пройтись по жизненному циклу фрагмента:

Q7: What is lifecycle of a fragment in android

Studio Bot: The lifecycle of a fragment in Android is a series of events that occur during the fragment's existence. These events are called lifecycle methods, and they are called in a specific order. The lifecycle methods are:

onCreate(): This method is called when the fragment is first created.

onStart(): This method is called when the fragment is about to be displayed to the user.

onResume(): This method is called when the fragment is displayed to the user.

onPause(): This method is called when the fragment is no longer visible to the user.

onStop(): This method is called when the fragment is no longer active.

onDestroyView(): This method is called when the fragment's view is destroyed.

onDestroy(): This method is called when the fragment is destroyed.

Колбеки onAttach() и onDetach() нам не так интересны, куда более важно отсутствие часто используемых методов onCreateView() and onViewCreated(). В добавок описание колбеков неточное, например, на onPause() пользователь всё ещё видит экран, но не взаимодействует с ним.

Затем Studio Bot привёл пример класса, который переопределяет все колбеки на Java. Я попросил переписать на Котлин, а после добавить пропущенные методы, с чем бот отлично справился.

В свою очередь, ChatGPT, несмотря на то, что предоставил более обширный жизненный цикл от onAttach() до onDetach(), включая onActivityCreated(), onViewStateRestored(), пропустил колбек onViewCreated(). Пришлось напомнить.

Теперь перейдём к практическому заданию, где мы будем оценивать работоспособность. Я хочу написать строковую функцию расширения, которая возвращает исходную строку, где каждая первая буква после пробела или дефиса капитализирована:

Q8: Create a String extension function that capitalize every character after space or dash

На выходе получаем следующее:

// Studio Bot
fun String.capitalizeAfterSpaceOrDash(): String {
  val sb = StringBuilder()
  for (c in this) {
    if (c == ' ' || c == '-') {
      sb.append(c.toUpperCase())
    } else {
      sb.append(c)
    }
  }
  return sb.toString()
}

По коду уже видно, что оно работает не так, как нам нужно. Вместо приведения в верхний регистр первой буквы ПОСЛЕ пробела или тире, он капитализирует САМИ тире и пробелы. Также странным кажется использование StringBuilder() класса.

Перед тем, как указать на ошибки, я попросил убрать устаревшие методы, ответ удивил:

Q8a: remove deprecated methods

// Studio Bot
package com.example.myapplication

import android.os.Bundle
import android.view.View
import android.widget.Button

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val button = findViewById<Button>(R.id.button)
        button.setOnClickListener {
            // do something
        }
    }
}

Studio Bot потерял контекст. 

Поэтому в следующем запросе я объединил первые два и получил следующее:

Q8b: create a String extension function that capitalize first character after space or dash and keeps others lowercase. Do not use any deprecated methods

// Studio Bot
fun String.capitalizeAfterSpaceOrDash(): String {
  val sb = StringBuilder()
  for (c in this) {
    if (c == ' ' || c == '-') {
      sb.append(c.toUpperCase())
    } else {
      sb.append(c.toLowerCase())
    }
  }
  return sb.toString()
}

И бот вернул нам ту же функцию с устаревшими методами. На этом я решил остановиться и посмотреть, как дела у ChatGPT.

Первое, что он выдал:

// ChatGPT
fun String.capitalizeAfterSpaceOrDash(): String {
    val words = this.split(" ", "-")
    val capitalizedWords = words.map { it.capitalize() }
    return capitalizedWords.joinToString(" ")
}

В принципе она работает, но с недочётом: функция меняет все тире на пробел. Просим сохранить сепаратор, убрать устаревшие методы и чтобы все остальные буквы приводились в нижний регистр:

// ChatGPT
fun String.capitalizeAfterSpaceOrDash(): String {
    val separatorRegex = Regex("(\\s|-)")
    val words = this.split(separatorRegex)
    val capitalizedWords = words.mapIndexed { index, word ->
        if (index != words.lastIndex) {
            val separator = separatorRegex.find(this, word.length)?.value ?: ""
            "$word$separator"
        } else {
            word
        }.replaceFirstChar { char -> char.uppercase() }.toLowerCase()
    }
    return capitalizedWords.joinToString("")
}

Получили рабочую функцию. 

Далее я решил протестировать функцию Ask Studio Bot, чтобы узнать, насколько хорошо он понимает логику работы фрагментов кода. Для этого я воспользовался первой функцией чата из предыдущего вопроса.

Q9: Explain the following code? 

fun String.capitalizeAfterSpaceOrDash(): String {

    val words = this.split(" ", "-")

    val capitalizedWords = words.map { it.capitalize() }

    return capitalizedWords.joinToString(" ")

}

// Studio Bot
fun String.capitalizeAfterSpaceOrDash(): String {
    // Split the string into words, using spaces and dashes as delimiters
    val words = this.split(" ", "-")

    // Capitalize the first letter of each word
    val capitalizedWords = words.map { it.capitalize() }

    // Join the words back together, separated by spaces
    return capitalizedWords.joinToString(" ")
}

Studio Bot:

fun String.capitalizeAfterSpaceOrDash(): String - This is a function that takes a string as input and returns a new string with the first letter of each word capitalized.

val words = this.split(" ", "-") - This line splits the input string into words, using spaces and dashes as delimiters.

val capitalizedWords = words.map { it.capitalize() } - This line capitalizes the first letter of each word in the words array.

return capitalizedWords.joinToString(" ") - This line joins the words in the capitalizedWords array back together, separated by spaces.

Затем я решил спросить, какие улучшения он может предложить:

Q9a: What improvements can be done to the function?

Studio Bot: Sure, here are a few improvements that could be made to the function:

The function could be made more efficient by using a regular expression to split the string into words…

The function could be made more readable by using more descriptive variable names. For example, the words variable could be renamed to wordList, and the capitalizedWords variable could be renamed to capitalizedWordList.

The function could be made more flexible by allowing the user to specify which characters should be used as delimiters…

Также бот привёл исправленный вариант:

// Studio Bot
fun String.capitalizeAfterSpaceOrDash(delimiters: String = " "): String { 
    // Split the string into words, using the specified delimiters 
    val words = this.split(delimiters) 
    // Capitalize the first letter of each word 
    val capitalizedWords = words.map { it.capitalize() } 
    // Join the words back together, separated by the specified delimiters             
    return capitalizedWords.joinToString(delimiters) 
} 

Принимаем пункт про использование регулярных выражений. По остальным остались вопросы: в такой маленькой функции не возникает проблема в читабельности переменных words и capitalizedWords  – аргумент притянут за уши; учитывая название функции тут не предполагается использование кастомных делиметров.

В результате мы просто получили построчное объяснение функции, без погружения в логику работы.

Зададим ещё один подобный вопрос. В активити я добавил ресивер, ориентированный на перезапуск системы.

Q10: Explain the following code:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_main)

    }

    private val receiver = object : BroadcastReceiver() {

        override fun onReceive(p0: Context?, intent: Intent?) {

            if (intent?.action != null && intent.action == Intent.ACTION_BOOT_COMPLETED) {

                print("Boot completed")

            }

        }

    }

}

Бот дважды (зачем?) описал функционал, привожу более подробный:

Studio Bot:

The MainActivity class extends the AppCompatActivity class. This means that the MainActivity class inherits all of the functionality from the AppCompatActivity class.

The onCreate() method is called when the activity is first created. The onCreate() method in the MainActivity class sets the content view for the activity and then calls the onCreate() method from the AppCompatActivity class.

The setContentView() method sets the content view for the activity. The content view is the layout that will be displayed on the screen.

The private val receiver = object : BroadcastReceiver() line declares a private variable named receiver that is an instance of the BroadcastReceiver class. The BroadcastReceiver class is used to receive broadcast intents.

The override fun onReceive(p0: Context?, intent: Intent?) line overrides the onReceive() method from the BroadcastReceiver class. The onReceive() method is called when the receiver receives an broadcast intent.

The if (intent?.action != null && intent.action == Intent.ACTION_BOOT_COMPLETED) line checks if the intent that was received has the action Intent.ACTION_BOOT_COMPLETED. The Intent.ACTION_BOOT_COMPLETED action is broadcast when the device boots up.

The print("Boot completed") line prints the message "Boot completed" to the console.

Как мы видим, здесь опять построчное описание.

В это время ChatGPT описывает функционал более подробно, акцентирует внимание на деталях и делает общий вывод о логике. Привожу ответы ниже.

Функция:

ChatGPT: The code you provided defines an extension function called capitalizeAfterSpaceOrDash() on the String class. This function capitalizes the first letter of each word in a string that comes after a space or a dash. Let's break down what the code does step by step:

1. The function takes a string (this) as the receiver object, which represents the string on which the function is being called.

2. The string is split into individual words using the split(" ", "-") function. The argument to split() specifies the delimiters used to split the string. In this case, it splits the string at spaces and dashes.

3. The words variable now holds a list of individual words.

4. The map() function is used to create a new list called capitalizedWords. For each word in the words list, the capitalize() function is applied to capitalize the first letter of the word.

5. Finally, the joinToString(" ") function is called on capitalizedWords to join the words back together into a single string. The argument to joinToString() specifies the separator used to separate the words. In this case, it uses a space character as the separator.

6. The resulting capitalized words are returned as a single string.

Активность:

ChatGPT: Overall, this code sets up the MainActivity class with a layout, and defines a BroadcastReceiver to handle the ACTION_BOOT_COMPLETED broadcast. When the device finishes booting, the code prints a message to indicate that the boot process has completed.

И финальное задание – написать мини-приложение заметок. Приложение не должно работать и проверять его работоспособность в целом не будем. Вопрос в том, насколько качественный материал мы получим, исходя из запроса.

Итак, сам вопрос:

Q11a: I want to build a simple note app. Provide all dependencies and code snippets meeting the following condition:

1.  Use Room database. Database includes one Note entity with title and text.

2. Use Kotlin Coroutines and Flows for i/o operations.

3. App consists of one activity and one view model.

4. Main screen contains recycler view with notes and floating action button to add new one.

Что мы ожидаем:

  1. Зависимости для ресайклера, вью модели, корутин, базы данных.

  2. Создание бд, entity и dao.

  3. Создание вью модели

  4. Ресайклер

  1. Зависимости:

// Studio Bot
dependencies { 
    implementation 'androidx.room:room-runtime:2.4.2'
    implementation 'androidx.room:room-ktx:2.4.2' 
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1' 
    implementation 'androidx.recyclerview:recyclerview:1.2.1' 
    implementation 'com.google.android.material:material:1.5.0' 
}

Бот пропустил зависимость для компилятора. Все остальные вроде на месте.

Chat GPT ответил так:

// Chat GPT

// Room database
implementation 'androidx.room:room-runtime:2.4.0'
kapt 'androidx.room:room-compiler:2.4.0'

// Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0'

// RecyclerView
implementation 'androidx.recyclerview:recyclerview:1.2.1'

// Material Design components
implementation 'com.google.android.material:material:1.4.0'

Чат сформировал их более красиво, но результат хуже: отсутствует зависимость для вью модели, а также для kapt нужен отдельный плагин, иначе наш проект не синхронизируется.

  1. БД и компоненты

// Studio Bot

// Define the Note entity
data class Note(
    val id: Int,
    val title: String,
    val text: String
)

// Create the Room database
val db = Room.databaseBuilder(
    applicationContext,
    NoteDatabase::class.java,
    "notes-database"
).build()

Мне не хотелось бы приводить сюда весь ответ, так как он займёт много места, поэтому рекомендую посмотреть полную версию. Но вот заключение: в сгенерированном коде вообще нет упоминания о классе NoteDatabase(), равно как и NoteDao(), которое бот упоминает в функции ниже. В дата классе Note() отсутствует аннотации и первичный ключ.

// Studio Bot
...
fun getAllNotes(): Flow<List<Note>> {
    // Get all notes from the database
    return db.noteDao().getAll()
}

Здесь чат уже справился намного лучше. Я приведу только БД, потому что считаю это наиболее сложным из всех. Все остальные компоненты также прописаны в отдельных участках кода на манер разных файлов, указаны импорты. Выглядит удовлетворительно.

// ChatGPT

@Database(entities = [Note::class], version = 1, exportSchema = false)
abstract class NoteDatabase : RoomDatabase() {
    abstract fun noteDao(): NoteDao
    companion object {
        @Volatile
        private var instance: NoteDatabase? = null
        fun getInstance(context: Context): NoteDatabase =
            instance ?: synchronized(this) {
                instance ?: buildDatabase(context).also { instance = it }
            }
        private fun buildDatabase(context: Context): NoteDatabase =
            Room.databaseBuilder(
                context, 
                NoteDatabase::class.java, 
                "note_database"
            )
                .fallbackToDestructiveMigration()
                .build()
    }
}
  1. View Model

Вью модель бот добавил в один файл с активити. 

// Studio Bot

// Define the view model
class NoteViewModel : ViewModel() {
    private val repository: NoteRepository by inject()
    val notes: LiveData<List<Note>> = repository.getAllNotes()
    fun addNote(title: String, text: String) {
        repository.addNote(Note(title = title, text = text))
    }
}
// Инициализация в MainActivity()
private val viewModel: NoteViewModel by viewModels()

Используемого NoteRepository() в ответе также не было и инжекнуть его без дополнительных приспособлений не получится. Также как и для инициализации вью модели, следовательно, нужна дополнительная зависимость.

У чата viewModel выглядит поприличнее, и мы видим использование корутин:

// ChatGPT
...
class NoteViewModel(private val noteDao: NoteDao) : ViewModel() {

    val allNotes = noteDao.getAllNotes().asLiveData()

    fun insert(note: Note) {
        viewModelScope.launch(Dispatchers.IO) {
            noteDao.insert(note)
        }
    }
}

Инициализация немного отличается, но вариант тоже нерабочий:

// ChatGPT
...
private val noteViewModel: NoteViewModel by viewModels {
    NoteViewModelFactory(NoteDatabase.getInstance(this).noteDao())
}
...
  1. Ресайклер

В первом ответе Studio Bot не предоставил нам адаптер, хотя и упомянул его в активити, поэтому я попросил написать его отдельно.

Q11b: create a recycler view for holding list of Note

В результате получил вёрстку для элемента списка, адаптер и вьюхолдер на Java. Есть замечание: не указан кастомный вьюхолдер в дженерике, несмотря на то, что дальше он используется в колбеке.

// Studio Bot

public class NoteAdapter extends RecyclerView.Adapter { 
    private List notes; 
    public NoteAdapter(List notes) { this.notes = notes; } 
    @Override 
    public NoteAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,   int viewType) {...}
    @Override 
    public void onBindViewHolder(NoteAdapter.ViewHolder holder, int position) {...}
    @Override public int getItemCount() { return notes.size(); } 
    public class ViewHolder extends RecyclerView.ViewHolder {...}
  

Chat GPT предоставил вёрстку активити с ресайклером, вёрстку элемента, адаптер и вью холдер и даже добавил дифутили.

На этом наше общение подошло к концу. Кому интересно - можете ознакомиться с полной версией.

Вывод

В перспективе Android Studio Bot может быть полезным помощником для разработчика, но не в ближайшее время. Несмотря на более-менее удобный функционал для взаимодействия с Android Studio (добавление зависимостей в build.gradle(.kts) и разрешений в AndroidManifest.xml, вставка кода в новый файл или по месту курсора и встроенные playground для теста), способность отвечать на базовые вопросы и старание подкрепить ответ примерами, по качеству обратной связи мы видим, что он всё ещё далек от совершенства: бот тяжело понимает (а иногда и теряет) контекст беседы; может придумывать методы; тяжело справляется даже с лёгкими задачами; приводит странные ссылки, на которые нельзя перейти и которые напрямую не относятся к задаче. Большинство ответов на данный момент легче будет найти на просторах интернета или в официальной документации, а также он сильно уступает современному гиганту ChatGPT, что делает его на данный момент неконкурентоспособным и неэффективным.

Комментарии (2)


  1. Veygard
    01.08.2023 12:52
    +1

    Спасибо за статью! Переодически использую гпт-4 для "автономных" задач по андроиду. В целом доволен. Галлюцинации у гпт бывают, но не так часто. И зачастую получается решать задачи быстрее, чем гугление.


    1. AmetKh Автор
      01.08.2023 12:52

      Я периодически использую чат чтобы подробно разжевать интересующую тему, очень выручает в этом плане, что можно задавать уточняющие вопросы, убрать лишние сомнении и тем самым лучше закрепить материал