Cara Push Notification Firebase dengan Kotlin: Panduan Lengkap untuk Developer Android

Fitur push notification adalah salah satu komponen vital dalam aplikasi mobile modern. Ini bukan sekadar alat untuk memberi tahu pengguna tentang update terbaru, tetapi juga sarana ampuh untuk meningkatkan retensi, engagement, dan bahkan mengarahkan tindakan spesifik. Bagi developer Android, Firebase Cloud Messaging (FCM) adalah solusi yang paling populer dan efisien untuk mengimplementasikan fungsionalitas ini. FCM memungkinkan Anda mengirim notifikasi ke satu perangkat, grup perangkat, atau bahkan seluruh basis pengguna Anda dengan mudah dan skala besar.

Sebagai seorang developer yang sering berkutat dengan aplikasi Android, saya sering melihat banyak yang mengalami kesulitan saat pertama kali mengintegrasikan FCM, terutama dengan konfigurasi yang benar di sisi aplikasi dan server. Dalam panduan ini, kita akan bedah tuntas cara mengimplementasikan push notification Firebase menggunakan Kotlin, mulai dari setup proyek hingga penanganan notifikasi di dalam aplikasi Android Anda. Kita akan bahas langkah demi langkah, lengkap dengan tips dan trik yang saya dapatkan dari pengalaman di berbagai proyek.

Mari kita mulai!

Daftar Isi sembunyikan

Prasyarat Sebelum Memulai

Sebelum kita terjun ke langkah-langkah teknis, pastikan Anda memiliki beberapa hal berikut:

  • Android Studio: Versi terbaru terinstal di komputer Anda.
  • Akun Google: Diperlukan untuk mengakses Firebase.
  • Proyek Android: Aplikasi Android yang sudah ada atau proyek baru yang siap diintegrasikan dengan Firebase. Pastikan aplikasi Anda memiliki nama package yang unik (misalnya, com.yourcompany.yourapp).
  • Koneksi Internet: Untuk mengunduh dependensi dan berinteraksi dengan layanan Firebase.

Langkah 1: Setup Proyek Firebase

Langkah pertama adalah menyiapkan proyek Anda di Firebase Console.

1.1 Buat Proyek Firebase Baru

  1. Buka Firebase Console.
  2. Klik Add project.
  3. Masukkan nama proyek Anda (misalnya, “MyApp Push Notification”). Klik Continue.
  4. Aktifkan atau nonaktifkan Google Analytics sesuai kebutuhan Anda. Untuk tutorial ini, kita bisa menonaktifkannya jika tidak diperlukan. Klik Continue.
  5. Tunggu hingga proyek Anda selesai dibuat.

1.2 Daftarkan Aplikasi Android Anda ke Firebase

  1. Setelah proyek dibuat, di halaman overview proyek, klik ikon Android (gambar robot hijau) untuk menambahkan aplikasi Android.
  2. Masukkan Android package name aplikasi Anda (ini harus sama persis dengan applicationId di file build.gradle level aplikasi Anda).
  3. (Opsional) Masukkan App nickname.
  4. (Opsional) Masukkan SHA-1 signing certificate debug key Anda. Ini penting untuk beberapa layanan Firebase, seperti otentikasi telepon, tetapi tidak wajib untuk FCM dasar. Untuk mendapatkannya, buka terminal di Android Studio dan jalankan perintah:
    • Di Linux/macOS: keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
    • Di Windows: keytool -list -v -keystore "%USERPROFILE%\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android
    • Salin nilai SHA-1 dan tempelkan di Firebase Console.

  5. Klik Register app.

1.3 Unduh File google-services.json

Setelah mendaftarkan aplikasi, Anda akan diminta untuk mengunduh file google-services.json. Ini adalah file konfigurasi penting yang berisi semua informasi proyek Firebase Anda.

  • Unduh file tersebut.
  • Tempatkan file google-services.json di direktori app/ (atau module/) proyek Android Anda. Pastikan nama filenya tidak berubah.

Langkah 2: Tambahkan Dependensi Firebase ke Proyek Android

Setelah file konfigurasi berada di tempatnya, kita perlu menambahkan SDK Firebase ke proyek Android Anda.

2.1 Konfigurasi build.gradle (Proyek Level)

Buka file build.gradle level proyek Anda (biasanya bernama build.gradle (Project: YourProjectName)). Tambahkan Google Services plugin dan Google’s Maven repository:

Di dalam blok buildscript > repositories, pastikan ada google() dan mavenCentral().

Di dalam blok buildscript > dependencies, tambahkan baris berikut:

  • classpath 'com.google.gms:google-services:4.4.1' (Gunakan versi terbaru yang direkomendasikan).

Contoh struktur:

buildscript {
  repositories {
    google()
    mavenCentral()
  }
  dependencies {
    classpath 'com.android.tools.build:gradle:8.2.0' (Sesuaikan dengan versi Gradle Anda)
    classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0' (Sesuaikan dengan versi Kotlin Anda)
    classpath 'com.google.gms:google-services:4.4.1'
  }
}

allprojects {
  repositories {
    google()
    mavenCentral()
  }
}

task clean(type: Delete) {
  delete rootProject.buildDir
}

2.2 Konfigurasi build.gradle (Aplikasi Level)

Buka file build.gradle level aplikasi Anda (biasanya bernama build.gradle (Module: app)). Tambahkan plugin dan dependensi untuk Firebase Cloud Messaging:

  1. Di bagian paling atas file, tambahkan plugin Google Services:
    • apply plugin: 'com.android.application'
    • apply plugin: 'org.jetbrains.kotlin.android'
    • apply plugin: 'com.google.gms.google-services'
  2. Di dalam blok dependencies, tambahkan dependensi FCM dan Firebase BoM (Bill of Materials) untuk mengelola versi:
    • implementation(platform("com.google.firebase:firebase-bom:32.7.4")) (Gunakan versi terbaru yang direkomendasikan)
    • implementation("com.google.firebase:firebase-messaging-ktx")
    • implementation("com.google.firebase:firebase-analytics-ktx") (Opsional, jika Anda mengaktifkan Google Analytics)

Contoh struktur:

plugins {
  id 'com.android.application'
  id 'org.jetbrains.kotlin.android'
  id 'com.google.gms.google-services'
}

android {
  // ... konfigurasi lainnya
}

dependencies {
  implementation(platform("com.google.firebase:firebase-bom:32.7.4"))
  implementation("com.google.firebase:firebase-messaging-ktx")
  implementation 'androidx.core:core-ktx:1.12.0'
  implementation 'androidx.appcompat:appcompat:1.6.1'
  implementation 'com.google.android.material:material:1.11.0'
  implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
  testImplementation 'junit:junit:4.13.2'
  androidTestImplementation 'androidx.test.ext:junit:1.1.5'
  androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}

Sinkronkan proyek Anda dengan Gradle setelah menambahkan dependensi.

Langkah 3: Menerima dan Menangani Notifikasi di Aplikasi Android (Kotlin)

Sekarang, kita akan membuat kode untuk menerima dan menampilkan notifikasi di aplikasi Anda.

3.1 Buat Service FirebaseMessagingService Kustom

Anda perlu membuat class baru yang meng-extend FirebaseMessagingService. Class ini akan bertanggung jawab untuk menerima token registrasi perangkat dan menangani pesan FCM yang masuk.

Buat file Kotlin baru (misalnya, MyFirebaseMessagingService.kt) di paket aplikasi Anda.

Di dalam class ini, Anda akan meng-override dua fungsi utama:

  1. onNewToken(token: String): Fungsi ini dipanggil setiap kali token registrasi FCM baru dibuat. Ini terjadi saat aplikasi diinstal pertama kali, saat pengguna menghapus data aplikasi, atau saat token kedaluwarsa. Anda harus mengirim token ini ke server backend Anda agar server dapat mengirim notifikasi ke perangkat spesifik ini.
  2. onMessageReceived(remoteMessage: RemoteMessage): Fungsi ini dipanggil ketika aplikasi Anda menerima pesan FCM. Cara notifikasi ini ditampilkan bergantung pada apakah aplikasi Anda berada di latar depan (foreground) atau latar belakang (background) dan jenis pesan yang dikirim (notification message atau data message).

Contoh struktur class MyFirebaseMessagingService.kt:

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.media.RingtoneManager
import android.os.Build
import android.util.Log
import androidx.core.app.NotificationCompat
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage

class MyFirebaseMessagingService : FirebaseMessagingService() {

  private val TAG = "MyFirebaseMsgService"

  override fun onNewToken(token: String) {
    Log.d(TAG, "Refreshed token: $token")
    // Jika Anda perlu mengirim token ke server backend Anda, lakukan di sini.
    // Misalnya: sendRegistrationToServer(token)
  }

  override fun onMessageReceived(remoteMessage: RemoteMessage) {
    Log.d(TAG, "From: ${remoteMessage.from}")

    // Cek apakah pesan berisi payload data.
    if (remoteMessage.data.isNotEmpty()) {
      Log.d(TAG, "Message data payload: ${remoteMessage.data}")
      // Tangani data message di sini jika aplikasi di foreground
      // atau jika pesan adalah 'data-only' message.
    }

    // Cek apakah pesan berisi payload notifikasi.
    remoteMessage.notification?.let {
      Log.d(TAG, "Message Notification Body: ${it.body}")
      // Tampilkan notifikasi jika aplikasi di foreground dan menerima notifikasi.
      sendNotification(it.title ?: "Tubianto.com", it.body ?: "Pesan baru!")
    }
  }

  private fun sendNotification(title: String, messageBody: String) {
    val intent = Intent(this, MainActivity::class.java)
    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
    val pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
      PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE)

    val channelId = getString(R.string.default_notification_channel_id) // Definisikan di strings.xml
    val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
    val notificationBuilder = NotificationCompat.Builder(this, channelId)
      .setSmallIcon(R.drawable.ic_stat_ic_notification) // Ganti dengan ikon notifikasi Anda
      .setContentTitle(title)
      .setContentText(messageBody)
      .setAutoCancel(true)
      .setSound(defaultSoundUri)
      .setContentIntent(pendingIntent)

    val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

    // Untuk Android O (8.0) dan di atasnya, Anda harus membuat Notification Channel.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      val channel = NotificationChannel(channelId,
        "Channel Human Readable Title",
        NotificationManager.IMPORTANCE_DEFAULT)
      notificationManager.createNotificationChannel(channel)
    }

    notificationManager.notify(0 /* ID of notification */, notificationBuilder.build())
  }
}

Beberapa hal penting dari kode di atas:

  • sendNotification function: Ini adalah fungsi helper untuk membangun dan menampilkan notifikasi ke status bar. Perhatikan penggunaan NotificationCompat.Builder untuk kompatibilitas lintas versi Android.
  • PendingIntent: Ini menentukan apa yang terjadi saat pengguna mengetuk notifikasi. Di sini, kita akan membuka MainActivity.
  • channelId: Untuk Android 8.0 (Oreo) ke atas, semua notifikasi harus dikaitkan dengan Notification Channel. Ini memungkinkan pengguna mengontrol kategori notifikasi secara lebih detail. Anda harus mendefinisikan default_notification_channel_id di file res/values/strings.xml Anda.
  • setSmallIcon: Wajib untuk notifikasi. Pastikan Anda memiliki ikon drawable (misalnya, ic_stat_ic_notification.xml) di folder res/drawable Anda. Jika tidak, notifikasi tidak akan muncul.

3.2 Deklarasikan Service di AndroidManifest.xml

Agar sistem Android tahu tentang MyFirebaseMessagingService Anda, Anda harus mendeklarasikannya di file AndroidManifest.xml di dalam tag <application>:

<application
  ...
  <service
    android:name=".MyFirebaseMessagingService"
    android:exported="false">
    <intent-filter>
      <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
  </service>
  ...
</application>

3.3 Dapatkan Token Registrasi (Opsional, untuk Debugging)

Anda bisa secara manual mendapatkan token FCM perangkat saat ini untuk tujuan pengujian. Ini berguna saat Anda ingin mengirim notifikasi ke perangkat spesifik dari Firebase Console atau alat lainnya.

Di MainActivity.kt (atau aktivitas mana pun), Anda bisa menambahkan kode berikut:

import com.google.firebase.messaging.FirebaseMessaging
import android.util.Log

// Dalam metode onCreate atau setelah aplikasi dimulai
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
  if (!task.isSuccessful) {
    Log.w(TAG, "Fetching FCM registration token failed", task.exception)
    return@addOnCompleteListener
  }

  // Dapatkan token FCM baru
  val token = task.result
  Log.d(TAG, "FCM Token: $token")
  // Anda bisa menampilkan token ini di UI atau menyalinnya untuk pengujian.
}

Jalankan aplikasi Anda, periksa Logcat untuk melihat token registrasi perangkat. Anda akan melihat log seperti “FCM Token: [YOUR_TOKEN_STRING]”.

Langkah 4: Kirim Notifikasi dari Firebase Console

Setelah setup di aplikasi selesai, saatnya menguji pengiriman notifikasi dari Firebase Console.

  1. Buka Firebase Console, pilih proyek Anda.
  2. Di menu sebelah kiri, navigasikan ke Engage > Messaging.
  3. Klik Send your first message (atau New campaign > Firebase Notification messages jika sudah ada kampanye).
  4. Notification title: Masukkan judul notifikasi Anda (misalnya, “Pesan Baru dari Tubianto.com”).
  5. Notification text: Masukkan isi pesan Anda (misalnya, “Ada artikel terbaru tentang AI dan Kotlin, cek sekarang!”).
  6. (Opsional) Notification image: Tambahkan URL gambar.
  7. Gulir ke bawah ke bagian Target. Pilih Android app dan pilih nama package aplikasi Anda.
  8. Gulir ke bawah ke bagian Scheduling. Pilih Now untuk mengirim segera.
  9. Gulir ke bawah ke bagian Additional options. Anda bisa menambahkan Custom data yang akan diterima di fungsi onMessageReceived Anda.
  10. Klik Review, lalu Publish.

Jika semua konfigurasi sudah benar, Anda akan melihat notifikasi muncul di perangkat Android Anda dalam beberapa detik atau menit.

Pengalaman dan Pertimbangan Praktis

Mengimplementasikan notifikasi bukan hanya tentang membuat notifikasi muncul, tetapi juga tentang bagaimana notifikasi itu diintegrasikan ke dalam user experience dan workflow aplikasi Anda. Berikut beberapa pertimbangan penting:

Notifikasi vs. Data Message

  • Notification Message (Display Message): Dirancang untuk ditampilkan kepada pengguna. FCM secara otomatis menangani tampilan notifikasi saat aplikasi berada di latar belakang (background) atau ditutup. Ketika aplikasi di latar depan (foreground), notifikasi ini diterima di onMessageReceived dan Anda harus secara manual menampilkannya (seperti yang kita lakukan di tutorial ini).
  • Data Message: Sepenuhnya ditangani oleh kode aplikasi Anda. Tidak ada UI yang dibuat secara otomatis oleh FCM. Pesan ini selalu diterima di onMessageReceived, baik aplikasi di latar depan maupun latar belakang. Ini ideal untuk mengirim payload data yang ingin Anda proses di aplikasi (misalnya, update data, sinkronisasi, dll.) tanpa harus menampilkan notifikasi visual.

Dalam praktiknya, banyak developer menggunakan gabungan keduanya (notification and data payload). Ini memungkinkan FCM menangani notifikasi display saat aplikasi di latar belakang, dan Anda tetap dapat memproses data kustom di latar depan atau ketika notifikasi diketuk.

Penanganan Notifikasi Saat Aplikasi di Latar Belakang (Background)

Saat aplikasi Anda berada di latar belakang atau ditutup, FCM akan secara otomatis menampilkan notifikasi yang datang dengan payload notification. Jika pengguna mengetuk notifikasi tersebut, aplikasi Anda akan diluncurkan, dan data dari notifikasi akan tersedia di Intent yang meluncurkan aktivitas utama Anda.

Untuk mengakses data ini, Anda bisa melakukannya di MainActivity:

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

  // ...
  if (intent.extras != null) {
    for (key in intent.extras!!.keySet()) {
      val value = intent.extras!!.getString(key)
      Log.d(TAG, "Key: $key Value: $value")
    }
  }
}

Optimasi Notification Channel (Android 8.0+)

Jangan anggap remeh pentingnya Notification Channel. Buat channel yang berbeda untuk berbagai jenis notifikasi yang Anda kirim. Misalnya, “Pembaruan Penting”, “Promosi”, “Obrolan”, dan sebagainya. Ini memberi pengguna kontrol penuh atas jenis notifikasi yang mereka terima dan dapat meningkatkan pengalaman pengguna secara signifikan. Definisikan nama dan deskripsi channel yang mudah dimengerti pengguna.

Ikon Notifikasi

Gunakan ikon kecil (smallIcon) yang transparan dan sesuai dengan pedoman desain Android. Ikon berwarna penuh atau yang tidak transparan akan terlihat buruk atau bahkan tidak muncul sama sekali di beberapa perangkat.

Deep Linking dengan Notifikasi

Anda bisa mengarahkan pengguna ke bagian spesifik dalam aplikasi Anda saat mereka mengetuk notifikasi. Ini dikenal sebagai deep linking. Untuk melakukannya, Anda perlu mengkonfigurasi PendingIntent di sendNotification agar mengarahkan ke aktivitas atau fragmen yang relevan, dan juga bisa menyertakan data tambahan dalam Intent tersebut.

Masalah yang Sering Terjadi

Berikut adalah beberapa masalah umum yang sering saya temui saat mengimplementasikan Firebase Push Notification, beserta solusinya:

1. Notifikasi Tidak Muncul Sama Sekali

  • Penyebab:
    • File google-services.json salah tempat atau tidak diunduh.
    • Plugin com.google.gms.google-services tidak ditambahkan di build.gradle level aplikasi.
    • Service MyFirebaseMessagingService belum dideklarasikan di AndroidManifest.xml.
    • Ikon kecil (setSmallIcon) tidak diatur atau ikonnya tidak valid.
    • Aplikasi di latar belakang dan notifikasi dikirim sebagai “data-only message” tanpa ditangani secara eksplisit.
    • Notification Channel tidak dibuat atau ID channel tidak cocok (untuk Android 8.0+).
  • Solusi:
    • Pastikan google-services.json ada di folder app/.
    • Verifikasi kedua file build.gradle sudah benar.
    • Cek deklarasi service di AndroidManifest.xml.
    • Pastikan ada drawable valid untuk setSmallIcon.
    • Uji dengan mengirim notifikasi dari Firebase Console (yang merupakan notification message).
    • Pastikan channelId di sendNotification sama dengan ID channel yang dibuat.

2. Notifikasi Hanya Muncul Saat Aplikasi Berada di Latar Depan (Foreground)

  • Penyebab: Anda mungkin hanya menerima “data-only message” dari server backend Anda. Ketika aplikasi di latar belakang, FCM tidak secara otomatis menampilkan notifikasi untuk jenis pesan ini.
  • Solusi: Pastikan backend Anda mengirim payload notification (misalnya, dengan judul dan badan pesan) selain payload data. Jika Anda harus mengirim data-only message ke aplikasi yang di latar belakang, Anda perlu menggunakan WorkManager atau FCM’s high-priority messages untuk memproses pesan tersebut di latar belakang dan secara manual membuat notifikasi.

3. Token FCM Tidak Diperbarui atau Tidak Didapatkan

  • Penyebab:
    • Kesalahan koneksi jaringan.
    • Firebase tidak terinisialisasi dengan benar.
    • Metode onNewToken tidak dipanggil atau tidak log tokennya.
  • Solusi:
    • Pastikan perangkat memiliki koneksi internet yang stabil.
    • Cek Logcat untuk pesan error terkait Firebase.
    • Jalankan aplikasi, hapus data aplikasi, atau uninstal/instal ulang untuk memicu pembuatan token baru.
    • Pastikan Anda memanggil FirebaseMessaging.getInstance().token.addOnCompleteListener di MainActivity untuk debugging.

4. Notifikasi Terlihat Generik (Tidak Ada Ikon, Suara Default)

  • Penyebab:
    • Ikon kecil tidak diatur atau salah.
    • Pengaturan suara atau warna tidak disesuaikan di NotificationCompat.Builder.
    • Untuk Android 8.0+, channel notifikasi tidak dikonfigurasi dengan properti suara, cahaya, atau getaran yang sesuai.
  • Solusi:
    • Atur setSmallIcon() dengan ikon yang benar.
    • Gunakan setSound(), setColor(), setVibrate() di builder notifikasi.
    • Saat membuat NotificationChannel, atur setSound(), setLightColor(), setVibrationPattern() sesuai kebutuhan. Ingat, setelah channel dibuat, perubahan pada properti channel tidak akan berlaku sampai pengguna menghapus data aplikasi atau menginstal ulang aplikasi.

FAQ

Bagaimana cara mengirim notifikasi ke topik tertentu?

Di sisi aplikasi, perangkat harus berlangganan topik menggunakan FirebaseMessaging.getInstance().subscribeToTopic("your_topic_name"). Kemudian, di Firebase Console atau server backend, Anda bisa menargetkan notifikasi ke topik tersebut.

Apakah saya perlu server backend sendiri untuk mengirim notifikasi?

Untuk kasus penggunaan dasar atau pengujian, Anda bisa mengirim notifikasi langsung dari Firebase Console. Namun, untuk aplikasi produksi, disarankan untuk memiliki server backend yang menggunakan Firebase Admin SDK untuk mengirim notifikasi. Ini memberi Anda kontrol lebih besar, personalisasi, dan kemampuan untuk mengintegrasikan notifikasi dengan logika bisnis Anda.

Mengapa notifikasi saya tidak berfungsi di iOS?

Panduan ini berfokus pada Android. Implementasi push notification di iOS juga menggunakan Firebase, tetapi memerlukan konfigurasi tambahan yang spesifik untuk Apple Push Notification service (APNs), seperti sertifikat APNs atau authentication key, dan juga konfigurasi di aplikasi iOS itu sendiri. Ini adalah topik yang berbeda.

Bisakah saya menyesuaikan layout notifikasi?

Ya, Anda bisa menggunakan custom layout untuk notifikasi. Ini melibatkan pembuatan layout XML Anda sendiri dan kemudian menggunakan NotificationCompat.Builder.setCustomContentView(). Namun, ini lebih kompleks dan harus digunakan dengan hati-hati agar tetap konsisten dengan pedoman desain Android.

Kesimpulan

Mengimplementasikan push notification Firebase dengan Kotlin di Android memang membutuhkan beberapa langkah konfigurasi dan pemahaman tentang bagaimana FCM bekerja. Namun, dengan mengikuti panduan ini secara cermat, Anda kini memiliki fondasi yang kuat untuk menambahkan fungsionalitas notifikasi yang andal dan powerful ke aplikasi Anda.

Ingatlah bahwa kunci sukses terletak pada pemahaman perbedaan antara notifikasi dan data message, penanganan yang tepat di latar depan dan latar belakang, serta penggunaan Notification Channel yang cerdas. Integrasi yang benar akan tidak hanya meningkatkan engagement pengguna tetapi juga membangun pengalaman aplikasi yang lebih responsif dan informatif. Selamat mencoba dan terus berkreasi!

TAGS: Firebase, Push Notification, Kotlin, Android, FCM, Developer Tools, Programming Tutorial, Mobile Development, Android Studio, Tech Ecosystem


Baca Juga

You May Also Like

Tinggalkan Balasan

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *