Hai, nama saya Andrey dan saya sedang mengerjakan aplikasi Tinkoff dan Tinkoff Junior untuk platform Android. Saya ingin berbicara tentang bagaimana kami mengumpulkan dua aplikasi serupa dari satu basis kode.
β , Μ 14 . , (, ), , , (, ).
. 

Pada awal proyek, kami mempertimbangkan berbagai opsi untuk penerapannya dan membuat sejumlah keputusan. Segera menjadi jelas bahwa kedua aplikasi (Tinkoff dan Tinkoff Junior) akan memiliki bagian signifikan dari kode umum. Kami tidak ingin melakukan fork dari aplikasi lama, lalu menyalin perbaikan bug dan fungsi umum yang baru. Untuk bekerja dengan dua aplikasi sekaligus, kami mempertimbangkan tiga opsi: Gradle Flavours, Git Submodules, Gradle Modules.
Rasa gradle
Banyak pengembang kami sudah mencoba menggunakan Flavours, ditambah lagi kami bisa menggunakan rasa multi-dimensi untuk digunakan dengan cita rasa yang ada.
Namun, Flavours memiliki satu kesalahan fatal. Android Studio menganggap kode hanya kode dari rasa aktif - yaitu, apa yang ada di folder utama dan folder rasa. Sisa kode dianggap teks bersama dengan komentar. Ini memberlakukan batasan pada beberapa alat studio: pencarian penggunaan kode, refactoring, dan lainnya.
Git submodules
Opsi lain untuk mengimplementasikan ide kami adalah menggunakan submodul dari git: mentransfer kode umum ke repositori terpisah dan menghubungkannya sebagai submodule ke dua repositori dengan kode untuk aplikasi spesifik.
Pendekatan ini meningkatkan kompleksitas bekerja dengan kode sumber proyek. Selain itu, pengembang masih harus bekerja dengan ketiga repositori untuk melakukan pengeditan saat mengubah API modul umum.
Arsitektur multi-modul
Opsi terakhir adalah beralih ke arsitektur multi-modul. Pendekatan ini bebas dari kerugian yang dimiliki dua lainnya. Namun, transisi ke arsitektur multi-modul membutuhkan refactoring yang memakan waktu.
Pada saat kami mulai bekerja di Tinkoff Junior, kami memiliki dua modul: modul API kecil yang menjelaskan cara bekerja dengan server, dan modul aplikasi monolitik besar, di mana sebagian besar kode proyek terkonsentrasi.


Sebagai hasilnya, kami ingin mendapatkan dua modul aplikasi: dewasa dan junior dan beberapa modul inti umum. Kami telah mengidentifikasi dua opsi:
- Menempatkan kode umum ke dalam modul umum umum. Pendekatan ini βlebih benarβ, tetapi butuh lebih banyak waktu. Kami memperkirakan volume penggunaan kembali kode sekitar 80%.

- Konversikan modul aplikasi menjadi pustaka dan hubungkan pustaka ini ke modul dewasa dan junior yang tipis. Opsi ini lebih cepat, tetapi akan membawa kode ke Tinkoff Junior yang tidak akan pernah dieksekusi.

Kami punya waktu cadangan, dan kami memutuskan untuk memulai pengembangan sesuai dengan opsi pertama (modul umum ) dengan syarat untuk beralih ke opsi cepat ketika kami kehabisan waktu untuk refactoring.
Pada akhirnya, ini terjadi: kami memindahkan sebagian proyek ke modul umum , dan kemudian mengubah modul aplikasi yang tersisa menjadi perpustakaan. Hasilnya, sekarang kami memiliki struktur proyek berikut:

Kami memiliki modul dengan fitur, yang memungkinkan kami membedakan antara kode "dewasa", umum atau "anak-anak". Namun, modul aplikasi masih cukup besar, dan sekarang sekitar setengah dari proyek disimpan di sana.
Mengubah aplikasi menjadi perpustakaan
Dokumentasi memiliki instruksi sederhana untuk mengubah aplikasi menjadi perpustakaan. Ini berisi empat poin sederhana dan, tampaknya, tidak ada kesulitan seharusnya:
- Buka file
build.gradle
modul - Hapus
applicationId
dari konfigurasi modul - Di awal file, ganti
apply plugin: 'com.android.application'
dengan apply plugin: 'com.android.library'
- Simpan perubahan dan sinkronisasi proyek di Android Studio ( File> Sinkronkan Proyek dengan File Gradle )
Namun, konversi membutuhkan waktu beberapa hari dan hasil yang dihasilkan berubah seperti ini:
- 183 file berubah
- 1601 insersi (+)
- Penghapusan 1920 (-)
Apa yang salah?
Pertama-tama, di perpustakaan, pengidentifikasi sumber daya bukanlah konstanta . Di perpustakaan, seperti dalam aplikasi, file R.java dihasilkan dengan daftar pengidentifikasi sumber daya. Dan di perpustakaan, nilai pengenal tidak konstan. Java tidak memungkinkan Anda untuk mengaktifkan nilai-nilai yang tidak konstan, dan semua switch harus diganti dengan if-else.
Selanjutnya, kami menemukan tabrakan paket.
Misalkan Anda memiliki perpustakaan yang memiliki package = com.example , dan aplikasi dengan package = com.example.app tergantung pada pustaka ini. Kemudian kelas com.example.R akan dihasilkan di perpustakaan, dan com.example.app.R , masing-masing , dalam aplikasi. Sekarang mari kita buat aktivitas com.example.MainActivity dalam aplikasi, di mana kita akan mencoba mengakses kelas-R. Tanpa impor eksplisit, kelas-R perpustakaan akan digunakan, di mana sumber daya aplikasi tidak ditentukan, tetapi hanya sumber daya perpustakaan. Namun, Android Studio tidak menyoroti kesalahan, dan ketika Anda mencoba beralih dari kode ke sumber daya, semuanya akan baik-baik saja.
Belati
Kami menggunakan Dagger sebagai kerangka kerja untuk injeksi ketergantungan.
Di setiap modul yang berisi aktivitas, fragmen, dan layanan, kami memiliki antarmuka biasa yang menggambarkan metode injeksi untuk entitas ini. Dalam modul aplikasi ( dewasa dan junor ), antarmuka komponen belati mewarisi dari antarmuka ini. Dalam modul, kami membawa komponen ke antarmuka yang diperlukan untuk modul ini.
Multibindings
Pengembangan proyek kami sangat disederhanakan dengan penggunaan multibindings.
Dalam salah satu modul umum, kami mendefinisikan sebuah antarmuka. Di setiap modul aplikasi ( dewasa , junior ) kami menjelaskan implementasi antarmuka ini. Menggunakan anotasi @Binds
, kami @Binds
belati bahwa setiap kali alih-alih sebuah antarmuka, perlu untuk menyuntikkan implementasi spesifiknya untuk aplikasi anak atau dewasa. Kami juga sering mengumpulkan kumpulan implementasi antarmuka (Set atau Peta), dan implementasi tersebut dijelaskan dalam modul aplikasi yang berbeda.
Rasa
Untuk tujuan yang berbeda, kami mengumpulkan beberapa opsi aplikasi. Rasa yang dijelaskan dalam modul dasar juga harus dijelaskan dalam modul tergantung. Juga, agar Android Studio berfungsi dengan benar, perlu dipilih opsi perakitan yang kompatibel di semua modul proyek.
Kesimpulan
Dalam waktu singkat kami telah menerapkan aplikasi baru. Sekarang kami mengirimkan fungsionalitas baru dalam dua aplikasi, menulisnya sekali.
Pada saat yang sama, kami menghabiskan beberapa waktu refactoring, secara bersamaan mengurangi utang teknis, dan beralih ke arsitektur multi-modul. Sepanjang jalan, kami menemukan batasan dari Android SDK dan Android Studio, yang berhasil kami kelola.