Dasar-dasar aplikasi

Pelajaran dari perjalanan WhatsApp di Wear OS

Speaker

  • Martin Pelant

    Software Engineer, Meta

  • Subhag Bhatia

    Software Engineer, Meta

Tentang

Tentang konferensi

Dengarkan langsung dari engineer WhatsApp tentang cara mereka mengupgrade aplikasi Wear OS dalam skala besar. Sesi ini membahas keputusan arsitektur dan UX di balik integrasi Material 3 dan penerapan Widget Wear.

Dapatkan insight langsung tentang pelajaran, pola, dan alat yang digunakan tim untuk mengupdate aplikasi mereka dan memberikan pengalaman pengguna yang kohesif.

Ringkasan

  • Berbagi kode ponsel Android dengan Wear OS mengurangi pekerjaan duplikat, tetapi memerlukan injeksi dependensi dan penggantian manifes. Pengujian harus membandingkan set multibinding agar fitur yang dimodularisasi tidak hilang secara diam-diam.
  • Pengelolaan penghubungan notifikasi runtime mencegah pemberitahuan duplikat sekaligus memungkinkan smartwatch beroperasi secara independen. Menonaktifkan bridging saat login dan mengaktifkannya kembali saat logout akan menghentikan duplikasi ponsel bergetar dua kali.
  • Migrasi Material 3 pada layar bulat memerlukan pengelolaan perubahan perilaku bersama dengan gaya visual. Menggunakan TransformingLazyColumn dengan benar berarti menerapkan transformasi hanya pada satu level dan menghindari posisi caching dengan remember Compose.
  • Batas masa berlaku aplikasi dapat memicu penurunan pengguna aktif secara tiba-tiba jika smartwatch gagal diupdate dengan cepat. Pengingat update terjadwal mengarahkan pengguna ke Google Play sebelum build sembilan puluh hari mereka habis masa berlakunya.

Gemini Notebook

Topik

  • wear-os
  • material-3
  • jetpack-compose
  • modularisasi
  • notifikasi
  • tiles-and-widgets

Transkrip

Pengantar

  • Halo semua.

  • Nama saya Martin.

  • Dan saya Subhag.

  • Kami adalah engineer di Meta dalam tim di balik WhatsApp di Wear OS.

  • Dan baru-baru ini kami memodernisasi aplikasi kami.

  • Jadi, hari ini kami akan membagikan pelajaran yang kami peroleh dari migrasi ke codebase bersama, lalu membangun pengalaman baru di atasnya.

  • Yang membuat WhatsApp di Wear OS menarik adalah aplikasi ini berfungsi tanpa ponsel Anda.

  • Setelah ditautkan, perangkat tersebut akan berfungsi sebagai perangkat WhatsApp tersendiri, yang dienkripsi end-to-end seperti ponsel Anda, dengan database dan koneksi ke servernya sendiri.

  • Jadi, fitur ini akan terus berfungsi bahkan di Wi-Fi atau LTE saat ponsel Anda berada di luar jangkauan.

  • Kami pertama kali meluncurkan WhatsApp di Wear OS pada tahun 2023.

  • Sistem ini didukung oleh klien portabel yang ringkas dan ditulis dalam Rust.

  • Kami dapat beralih ke tahap produksi dengan cepat.

  • Namun, ini adalah implementasi WhatsApp kedua yang independen, yang berarti bahwa setiap protokol, enkripsi, dan fitur harus dibuat dua kali.

  • Jadi, aplikasi smartwatch selalu tertinggal.

  • Karena portabel, penanganan latar belakang juga menjadi rumit.

  • Klien menangani thread, timer, dan soketnya sendiri, hal-hal yang ingin dikelola Android untuk Anda.

  • Di Android, latar belakang memiliki aturan.

  • Saat dalam mode istirahat, Anda akan kehilangan jaringan.

  • Setelah proses di-cache, Anda memiliki waktu eksekusi yang terbatas atau tidak ada sama sekali.

  • Anda memerlukan layanan, tugas, atau push untuk mengatasinya.

  • Jadi, aplikasi smartwatch kami harus menghubungkannya secara manual.

  • Namun, codebase Android kami sudah melakukannya.

  • Jadi, kami memutuskan untuk menggunakan kembali dan bermigrasi ke codebase Android kami, yang telah teruji di miliaran perangkat dan sudah mengikuti aturan Android.

Beralih ke codebase bersama

  • Dengan begitu, fitur baru tidak perlu dibuat dua kali.

  • Tujuannya sederhana: mempertahankan logika bisnis dan menghapus UI.

  • Yang tersisa adalah library headless yang menggunakan flow Kotlin sebagai API-nya.

  • Namun, tantangannya adalah Anda tidak dapat mengurai aplikasi sebesar ini sekaligus.

  • Beberapa modul menggabungkan UI dengan infrastruktur.

  • Dependensi lain bergantung pada fitur yang tidak Anda perlukan di aplikasi Wear OS.

  • Jadi, kami bekerja dalam dua jalur: kami terus melakukan modularisasi, dan kami menghapus atau menimpa apa yang belum dapat kami hapus.

  • Mari kita mulai dengan modularisasi.

  • Untuk mencegah kode masuk ke build, pernyataan sederhana "jika ini adalah aplikasi ponsel" tidak akan berfungsi karena tidak menghapus kode.

  • Jadi, kami sangat mengandalkan DI, terutama opsional dan multibinding.

  • Kami menggunakan DI internal, tetapi DI tersebut sangat mirip dengan yang ditawarkan Dagger.

  • Jadi, contohnya ada di Dagger.

  • Dengan binding opsional, di Dagger adalah BindsOptionalOf, Anda dapat mendeklarasikan dependensi sebagai opsional, misalnya memanggil backend.

    @Module
    interface CallingModule {
        @BindsOptionalOf
        fun callingBackend(): CallingBackend
    }
    
    class Calls @Inject constructor(
        val backend: Optional<CallingBackend>,
    ) {
        fun start(): CallResult =
            backend.getOrNull()?.start()
                ?: CallResult.NotAllowed
    }
  • Kemudian, jika build smartwatch menyertakan modul yang menyediakan implementasi, backend yang diinjeksikan akan ada, dan panggilan akan berfungsi.

  • Jika Anda menghilangkan modul, modul akan bernilai null, dan API Anda dapat menampilkan NotAllowed.

  • Multibinding berfungsi untuk hal yang sama, tetapi untuk set.

    @Module
    interface CoreModule {
        @Multibinds
        fun handlers(): Set<MessageHandler>
    }
    
    @Module
    interface ReactionsModule {
        @Binds @IntoSet
        fun reactions(impl: ReactionHandler):
     MessageHandler
    }
    
    class MessageDispatcher @Inject constructor(
          private val handlers: Set<MessageHandler>,
    )
  • Anda dapat mendeklarasikan set dengan multibind, misalnya MessageHandler, lalu setiap modul fitur dapat menambahkan pengendali mereka sendiri menggunakan multibinding ke dalam set.

  • Jika Anda tidak ingin mendukung pembayaran, misalnya Anda dapat menghapus seluruh modul pembayaran dari build, dan set yang diinjeksikan tidak akan menyertakan pengendali tersebut.

  • Dengan begitu, apakah fitur didukung atau tidak adalah pertanyaan untuk grafik build.

  • Namun, pemisahan ini memiliki dua sisi.

  • Dengan referensi langsung, menghapus modul akan merusak build Anda.

  • Dengan opsional atau multibinding, tidak ada yang mereferensikan modul, sehingga build lulus, dan fitur menghilang secara diam-diam, atau default akan diterapkan.

  • Dan kami berhasil mencapai hal ini.

  • Refaktorisasi memindahkan fitur ke modulnya sendiri, dan aplikasi build serta aplikasi smartwatch kami tidak menyertakannya.

  • Masalah ini menyebabkan pesan suara menampilkan durasi nol detik.

  • Jadi, bagaimana cara memastikan hal ini tidak akan terjadi?

  • Anda memerlukan pengujian untuk mengembalikan pemeriksaan.

  • Dalam kasus ini, yang kami lakukan adalah membandingkan set multibinding antara aplikasi Telepon dan aplikasi smartwatch untuk antarmuka yang penting.

  • Dengan begitu, kita memastikan bahwa kita selalu mengikat implementasi yang tepat.

  • Sekarang, jalur kedua mencakup apa yang belum dapat kami hapus.

  • Menggunakan kembali modul akan menghemat pekerjaan Anda, tetapi semua yang dideklarasikan akan digabungkan ke dalam manifes Anda.

  • Aktivitas peluncur ponsel akan menambahkan ikon aplikasi sekunder, atau penyedia konten yang tidak diperlukan akan dimulai dengan aplikasi Anda.

  • Penggabungan manifes menawarkan atribut tools:node="remove" yang berguna, yang memungkinkan Anda menghapus komponen.

    <!-- remove unused Main activity node -->
    <activity android:name="com.whatsapp.Main"
              tools:node="remove" />
    
    <!-- redirect any calls to a new Activity -->
    <activity-alias
         android:name="com.whatsapp.Main"
         android:targetActivity=".MyNewActivity" />
    
    <provider
    android:name="…MigrationContentProvider"
            tools:node="remove" />
  • Setelah dihapus, penyingkat juga dapat menghapus kode tersebut.

  • Jika masih ada yang perlu menjangkau aktivitas, Anda dapat menggunakan activity-alias untuk mengalihkan ke class baru.

  • Namun, hal yang perlu diperhatikan adalah atribut tersebut hanya berlaku untuk manifes berprioritas lebih rendah, sehingga urutan penggabungan menentukan apakah sesuatu akan dihapus atau tidak.

  • Resource juga berfungsi dengan cara yang sama.

  • Reuse memberi Anda string telepon secara gratis.

  • Namun, beberapa aplikasi menjanjikan hal-hal yang tidak dapat dilakukan smartwatch.

  • Mari kita ambil contoh di akhir pemberitahuan enkripsi yang ada di awal setiap chat.

  • Di ponsel, pesan ini diakhiri dengan link "pelajari lebih lanjut".

  • Namun, tidak ada apa pun di balik link di smartwatch.

  • Jadi, yang kita lakukan adalah mendeklarasikan ulang kunci yang sama dalam modul prioritas yang lebih tinggi di smartwatch dan mengganti string dengan "gunakan ponsel Anda untuk mempelajari lebih lanjut".

    <!-- phone -->
    <string name="…_disclosure">… Learn more</string>
    
    <!-- wearable override, same key -->
    <string name="…_disclosure">…Use your phone to learn more.</string>
  • Kemudian, penggabung resource akan memilih resource kita.

  • Anda juga memerlukan sesuatu untuk memastikan bahwa Anda tidak mengganti string secara tidak sengaja.

  • Jadi, kami telah membuat aturan build yang memastikan dan gagal jika penggantian tidak diizinkan.

Mengelola bridging notifikasi

  • Sekarang, mari kita bahas notifikasi.

  • Secara default, Wear OS menghubungkan atau mencerminkan notifikasi ponsel Anda.

  • Bagus, tetapi fitur ini hanya berfungsi jika ponsel Anda terhubung.

  • Kami juga ingin WhatsApp berfungsi secara independen, sehingga aplikasi kami memposting notifikasi kami sendiri.

  • Namun, jika bridging tetap aktif, setiap pesan akan bergetar dua kali.

  • Menonaktifkan penghubungan akan memperbaiki duplikat.

  • Ada flag Manifes statis, tetapi flag ini berlaku sejak penginstalan sebelum pengguna login.

  • Jadi, jika Anda menginstal dan tidak login, Anda akan tiba-tiba kehilangan semua notifikasi, yang tentu saja tidak bagus, bukan?

  • Jadi, kita menetapkannya saat runtime menggunakan BridgingManager.

    isRegistered.collect {
      when (it) {
             // watch posts its own
        true  -> bridging.setEnabled(false)
     // phone bridges again
        false -> bridging.setEnabled(true)
      }
    }
    
    fun setEnabled(bridgingEnabled){
       bridgingManager.setConfig(
    BridgingConfig.Builder(context,
    bridgingEnabled)
          .build())
    }
  • Saat Anda login, kami menonaktifkan penghubungan.

  • Saat Anda logout, kami akan mengaktifkannya kembali.

  • Namun, perlu diingat bahwa tidak ada API untuk membaca kembali nilai, dan kami mengamati smartwatch kehilangan nilai tersebut.

  • Jadi, yang kami lakukan adalah menulisnya lagi setiap kali smartwatch dilepas dari pengisi daya atau jika aplikasi diupdate.

  • Topik terakhir yang ingin saya bahas adalah, bagaimana cara logout?

  • Saat Anda membatalkan tautan smartwatch dari akun Anda, pengguna akan mengira bahwa pesan juga akan dihapus.

  • Jadi, kami menghapus semuanya-- pesan, kunci, setelan.

  • Namun, bagaimana cara melakukannya dengan aman?

  • Menghapus file saat thread latar belakang masih menulis ke file tersebut, Anda akan mendapatkan pengecualian SQLite, atau bahkan lebih buruk lagi, menulis ke database yang baru dibuat ulang.

  • Dan Anda tidak dapat menghapus status proses tanpa menghentikannya.

  • Android menawarkan clearApplicationUserData API, yang menghapus data dan menghentikan aplikasi, tetapi juga mereset izin.

  • Dan di beberapa OEM, penghubungan notifikasi terkait dengan izin notifikasi, jadi jika Anda menggunakan ini, Anda juga akan tiba-tiba kehilangan pencerminan ponsel.

  • Jadi, kami menghentikan prosesnya sendiri.

    clearActivityBackStack()
    
    // enumerate our own, kill each
    killOtherProcesses()
    
    // no threads alive — now safe
    deleteDataDirectories()
    
    // show logged out confirmation
    firePostCleanupIntent()
  • Kami membangun layanan kecil dalam proses sekunder, yang tidak memiliki DI, tidak memiliki pengelola tugas, sehingga tidak memulai bagian aplikasi lainnya.

  • Setelah Anda logout, layanan ini akan menghentikan setiap proses lain di aplikasi kita, dan baru menghapus semua data saat tidak ada yang bersaing dengannya.

  • Karena kita tidak menggunakan clearApplicationUserData, izin akan tetap ada dan kita dapat mengaktifkan penghubung notifikasi, sehingga notifikasi ponsel akan muncul kembali.

  • Itu untuk bagian pertama, dan sekarang saya akan menyerahkannya kepada Subhag untuk membahas pembaruan UI kami.

Memodernisasi UI Wear OS

  • Terima kasih, Martin.

  • Dengan infrastruktur bersama yang sudah ada, fokus kami adalah membuat WhatsApp terasa benar-benar nyaman di pergelangan tangan Anda.

  • Bagian pertama dari perjalanan tersebut adalah beralih ke Material 3, tetapi pertama-tama, mari kita tanyakan alasannya.

  • Nah, di layar bulat yang setiap pikselnya penting, Material 3 memberi kami komponen yang tepat dan dasar desain untuk memodernisasi WhatsApp untuk smartwatch.

  • Jika Anda perhatikan visualnya, sekilas, ini mungkin terlihat seperti update UI.

  • Kami beralih dari Material 2 ke 3, dan hal ini tidak dapat terjadi dalam semalam.

  • Kode lama dan baru harus berjalan berdampingan di balik tombol.

  • Akibatnya, layar yang secara visual cocok dengan Figma, masih menunjukkan beberapa perilaku yang rusak.

  • Sebagai contoh, scroll kenop, geser ke kanan untuk kembali, beberapa gestur paling dasar tidak berfungsi.

  • Jadi, migrasi sistem desain bukan hanya migrasi visual, tetapi juga migrasi perilaku.

  • Terkait perilaku, jika Anda perhatikan baris di dekat tepi, baris tersebut lebih sempit dan terselip di lengkungan.

  • Itulah TransformingLazyColumn.

  • Baris berubah sehingga tidak terpotong oleh bezel melingkar.

  • Layar dinamis dapat menyulitkan.

  • Pesan baru masuk, baris berpindah, dan balon muncul dalam berbagai bentuk.

  • Mari kita lihat apa yang dimaksud dengan melakukannya dengan benar.

  • Jika Anda perhatikan di sebelah kiri, gelembung menyusut terlalu agresif.

  • Setiap pesan secara internal adalah balon yang digabungkan di dalam kartu.

  • Kita memberi tahu kartu induk untuk menerapkan transformasi, tetapi transformasi yang sama juga diteruskan ke balon turunan.

  • Akibatnya, balon turunan mendapatkan jumlah transformasi yang diinginkan dua kali lipat.

  • Perbaikannya adalah memastikan bahwa kita menerapkan transformasi di satu tingkat, dan itu ada di kartu induk.

  • Dan itu juga merupakan prinsip utama.

  • Kami memastikan satu transformasi untuk satu item.

  • Dan inilah masalah lainnya: gelembung yang kecil dan pudar tepat di tengah layar, padahal seharusnya berukuran penuh.

  • Ukuran adalah fungsi dari posisinya di layar.

  • Dalam versi awal, kami menyimpan penyesuaian ukuran menggunakan fungsi remember Compose, tetapi hal itu juga akhirnya menyimpan posisi lamanya.

  • Akibatnya, saat pesan baru tiba dan menggeser balon lama ke atas, transformasinya masih didasarkan pada posisi lama.

  • Dan hal itu menyebabkan bug tersebut.

  • Untuk memperbaikinya, kita berhenti menyimpan ukuran menggunakan panggilan remember dan menghapus panggilan tersebut.

  • Dan itu juga merupakan pembelajaran.

  • Saat baris dipindahkan, transformasinya harus dipindahkan bersamanya.

  • Di sini, Anda dapat melihat kedua perbaikan dalam kode.

    // FIX #1: ONE OWNER
    CompositionLocalProvider(
      LocalSurfaceTransformation provides null
    ) {
      M3Card(transformation = t) {
        SharedChatEntry(...)
      }
    }
    
    // FIX #2: CURRENT ITEM SCOPE
    val t = SurfaceTransformation(spec)
    // BUG #1: DOUBLE MORPH
    M3Card(transformation = t) {
      CompositionLocalProvider(
        LocalSurfaceTransformation provides t
      ) { SharedChatEntry(...) }
    }
    
    // BUG #2: STALE ITEM SCOPE
    val t = remember(spec) {
      SurfaceTransformation(spec)
    }
  • Di kiri atas, Anda dapat melihat transformasi diterapkan di dua tingkat, dan di kanan atas, transformasi hanya diterapkan di satu tingkat, yaitu kartu M3.

  • Di kiri bawah, kita menggunakan panggilan remember untuk meng-cache transformasi, tetapi juga meng-cache posisi.

  • Dan di kanan bawah, kita menghitung transformasi dengan mempertimbangkan posisi terbaru gelembung setiap saat.

  • Seperti yang Anda lihat, hasilnya adalah pengalaman yang konsisten di semua layar, dengan konten utama tetap berada di dekat bagian tengah, dan baris bergerak secara alami di dekat tepi.

Menangani pemberitahuan notifikasi di pergelangan tangan

  • Selanjutnya, mari kita bahas notifikasi.

  • Sekarang bayangkan pergelangan tangan Anda bergetar, Anda memeriksa smartwatch, dan melihat pesan.

  • Namun, beberapa saat kemudian, smartwatch Anda bergetar lagi, dan lagi.

  • Anda mendapatkan beberapa notifikasi, dan setiap kali Anda memeriksanya, Anda melihat hal yang sama.

  • Wah, itu sangat menjengkelkan.

  • Jadi, untuk memperbaiki masalah ini, kami mengajukan dua pertanyaan kepada diri kami sendiri.

  • Peristiwa apa yang harus memicu notifikasi untuk memberi tahu pengguna?

  • Dan peristiwa mana yang harus memperbarui notifikasi secara otomatis?

  • Bug ini terjadi karena di balik layar, pembaruan foto profil, download media, dan tanda terima, yang seharusnya memperbarui notifikasi secara otomatis, justru memberi tahu pengguna.

  • Sekarang, saat pesan baru masuk, pergelangan tangan akan bergetar sekali, sementara peristiwa yang tidak memerlukan pemberitahuan akan memperbarui notifikasi secara otomatis.

  • Dan itu juga merupakan pelajaran.

  • Jika notifikasi adalah salah satu perjalanan pengguna inti Anda, kita harus memastikan bahwa setiap pemberitahuan benar-benar layak mendapatkan perhatian pengguna.

  • Terkait topik notifikasi.

  • Sebelumnya, notifikasi WhatsApp Wear OS memungkinkan Anda melakukan dua hal.

  • Anda dapat membaca pesan, atau membuka aplikasi.

  • Jika ingin membalas notifikasi secara langsung, Anda tidak dapat melakukannya sebelumnya.

  • Jadi, kami menjembatani kesenjangan tersebut menggunakan tindakan notifikasi Android dengan RemoteInput API.

  • Pengguna kini dapat mengetik, mengucapkan, atau mengetuk balasan cepat.

  • Namun, ingat, menekan tombol kirim bukanlah akhir.

  • Jika pesan berhasil dikirim, kami akan menandai chat sebagai telah dibaca dan menghapus notifikasi.

  • Jika pesan gagal dikirim, kami menawarkan opsi coba lagi dengan sekali ketuk.

  • Hal itu juga merupakan pembelajaran penting saat mengembangkan ini, yaitu bahwa tindakan notifikasi harus diperlakukan sebagai alur kerja menyeluruh yang lengkap.

Bermigrasi dari kartu ke widget

  • Berikutnya, Kartu dan Widget.

  • Kartu, seperti yang Anda lihat, adalah satu layar penuh, yang dapat Anda jangkau dengan menggeser dari tampilan jam.

  • Di sisi lain, widget adalah beberapa kartu yang lebih kecil, yang beberapa di antaranya dapat muat dalam satu layar yang dapat di-scroll.

  • Kartu dan Widget WhatsApp didukung oleh data pokok yang sama.

  • Dan itu juga merupakan twist.

  • Anda mungkin mengira bahwa UI ini digambar oleh aplikasi kita, tetapi sebenarnya tidak demikian.

  • Kita hanya mendeskripsikan UI dan data, dan sistem akan menggambarnya.

  • Selain itu, mengetuk ikon mana pun tidak dapat menjalankan kode aplikasi Anda.

  • Anda hanya dapat memintanya untuk membuka layar.

  • Demikian pelajaran kita.

  • Jika sistem menggambar UI Anda, hal ini merupakan bagian dari metrik produk dan pengujian Anda.

  • Frank dan Yuri akan membahas lebih dalam tentang Widget.

Mencegah penurunan pengguna aktif dengan pengingat update

  • Dan itu membawa saya ke slide terakhir, yaitu mari kita mundur selangkah dan bertanya pada diri sendiri, pada akhirnya, kita ingin semakin banyak pengguna yang menggunakan aplikasi kita.

  • Sekarang bayangkan sebagai developer, Anda login ke dasbor suatu hari, dan Anda melihat penurunan pengguna aktif yang signifikan.

  • Pertama-tama, hal ini akan sangat mengkhawatirkan.

  • Lalu, Anda mungkin mulai men-debug-nya.

  • Anda mungkin merasa bahwa terjadi error, atau ada rilis yang bermasalah.

  • Namun, bagi kami, tidak ada satu pun dari hal tersebut yang terjadi.

  • Ternyata setiap versi aplikasi WhatsApp memiliki masa berlaku bawaan selama 90 hari.

  • Jadi, bayangkan pengguna terjebak pada build yang sudah usang, sementara build yang lebih baru memerlukan waktu untuk menjangkau smartwatch mereka.

  • Jadi, pengguna tidak keluar dari aplikasi, tetapi aplikasi mereka tetap berhenti berfungsi.

  • Jadi, kami menjadikan update aplikasi sebagai bagian dari produk.

  • Seperti yang Anda lihat di layar, 15 hari sebelum masa berlaku aplikasi berakhir, pesan ini akan muncul di layar.

  • Kemudian, pesan akan muncul lagi saat tersisa 7, 5, 3, 2, dan 1 hari.

  • Pengguna dapat memilih untuk menutupnya, atau membuka Google Play Store untuk mengupdate aplikasi mereka.

  • Hal ini membantu dengan memberikan beberapa pengingat kepada pengguna bahwa masa berlaku aplikasi mereka akan segera berakhir, dan juga memberi mereka beberapa peluang untuk mengupdate aplikasi.

  • Dan itulah pembelajaran terakhir kita.

  • Tugas kita sebagai developer belum selesai saat kita mengirimkan update ke 100%.

  • Proses ini baru selesai setelah pengguna benar-benar menerima update kami.

  • Kita telah sampai di akhir.

  • Jika Anda ingin melanjutkan percakapan, silakan datang dan sapa kami selama waktu konsultasi.

  • Selanjutnya, Frank dan Yuri akan membahas Widget Wear.

  • Terima kasih.

Kode

Cuplikan kode

Cuplikan Kotlin pada 02:27 di "Beralih ke codebase bersama"
@Module
interface CallingModule {
    @BindsOptionalOf
    fun callingBackend(): CallingBackend
}

class Calls @Inject constructor(
    val backend: Optional<CallingBackend>,
) {
    fun start(): CallResult =
        backend.getOrNull()?.start()
            ?: CallResult.NotAllowed
}
Cuplikan Kotlin pada 02:47 di "Beralih ke codebase bersama"
@Module
interface CoreModule {
    @Multibinds
    fun handlers(): Set<MessageHandler>
}

@Module
interface ReactionsModule {
    @Binds @IntoSet
    fun reactions(impl: ReactionHandler):
 MessageHandler
}

class MessageDispatcher @Inject constructor(
      private val handlers: Set<MessageHandler>,
)
Cuplikan XML pada menit 04:16 di "Beralih ke codebase bersama"
<!-- remove unused Main activity node -->
<activity android:name="com.whatsapp.Main"
          tools:node="remove" />

<!-- redirect any calls to a new Activity -->
<activity-alias
     android:name="com.whatsapp.Main"
     android:targetActivity=".MyNewActivity" />

<provider
android:name="…MigrationContentProvider"
        tools:node="remove" />
Cuplikan XML pada 05:03 di "Beralih ke codebase bersama"
<!-- phone -->
<string name="…_disclosure">… Learn more</string>

<!-- wearable override, same key -->
<string name="…_disclosure">…Use your phone to learn more.</string>
Cuplikan Kotlin pada 05:59 di "Mengelola penghubung notifikasi"
isRegistered.collect {
  when (it) {
         // watch posts its own
    true  -> bridging.setEnabled(false)
 // phone bridges again
    false -> bridging.setEnabled(true)
  }
}

fun setEnabled(bridgingEnabled){
   bridgingManager.setConfig(
BridgingConfig.Builder(context,
bridgingEnabled)
      .build())
}
Cuplikan Kotlin pada 07:06 di "Mengelola penghubung notifikasi"
clearActivityBackStack()

// enumerate our own, kill each
killOtherProcesses()

// no threads alive — now safe
deleteDataDirectories()

// show logged out confirmation
firePostCleanupIntent()
Cuplikan Kotlin pada 10:30 di "Memodernisasi UI Wear OS"
// FIX #1: ONE OWNER
CompositionLocalProvider(
  LocalSurfaceTransformation provides null
) {
  M3Card(transformation = t) {
    SharedChatEntry(...)
  }
}

// FIX #2: CURRENT ITEM SCOPE
val t = SurfaceTransformation(spec)
Cuplikan Kotlin pada menit 10:30 di "Memodernisasi UI Wear OS"
// BUG #1: DOUBLE MORPH
M3Card(transformation = t) {
  CompositionLocalProvider(
    LocalSurfaceTransformation provides t
  ) { SharedChatEntry(...) }
}

// BUG #2: STALE ITEM SCOPE
val t = remember(spec) {
  SurfaceTransformation(spec)
}