Trong bài viết về kiến trúc MVP trong Android, mình đã từng giới thiệu thư viện Dragger2, một thư viện Dependency Injection mạnh mẽ và hữu ích dành cho phát triển Android. Bài viết này mình sẽ chia sẻ cách sử dụng Dagger 2 trong Android, rất rất cơ bản để các bạn mới cũng có thể hiểu và áp dụng được.
Trước đây khi mình mới học lập trình, mình cũng bắt đầu tìm hiểu về quy tắc viết clean code SOLID. Thực sự nó làm mình tẩu hỏa nhập ma với hàng loạt khái niệm như: Dependency Injection, Provider, Component, Module, Scope…
Do vậy, mình đã tự hứa với bản thân rằng khi nào mình đã hiểu rõ về quy tắc này thì lập tức phải làm ngay một bài viết để chia sẻ lại cho mọi người (cũng vừa giúp mình ghi nhớ lại).
Và đây là một phần trong kế hoạch đó, một bài viết về Dagger 2, một thư viện hỗ trợ bạn thực hiện nguyên tắc Dependency Injection( một phần trong triết lý SOLID).
Nội dung chính của bài viết
Tìm hiểu Dagger 2 trong Android
#Khởi tạo dự án Android
Trước khi bạn bắt đầu đọc bài viết này, bạn đã biết đến các khái niệm như Function, Class và Member Variables rồi đúng không?
Và bạn cũng biết cách tạo một dự án trong Android Studio rồi chứ? Thậm chí Android Studio bây giờ còn hỗ trợ tạo dự án với những tính năng cơ bản mà bạn không cần phải nhúng tay code.
Tuy nhiên, để tiện theo dõi, chúng ta sẽ tạo một dự án với một Empty Activity. Sau đó thêm một TextView.
Và đây là code của MainActivity:
class MainActivity : AppCompatActivity() { val info = Info() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) text_view.text = info.text } } class Info { val text = "Hello Dagger 2" }
#Chuyển Class Info ra khỏi Activity…
Trong các dự án thực tế, mình không chỉ có mỗi một biến info như thế kia. Ngoài ra, thông tin Info mình còn muốn chia sẻ giữa các Activity với nhau nữa.
Nếu chúng ta khởi tạo Info trong MainActivity sẽ làm cho code không được clean. Việc kế thừa code sẽ trở nên khó khăn hơn.
Do đó, thay vì chúng ta tạo một class Info trong MainActivity. Mình muốn thông tin ( và các biến thành viên khác nữa) được tạo ở một nơi khác.
Bạn có biết hành động này gọi là gì không? Đó chính là Dependencies Injection.
Lúc này code của chúng ta thay vì khỏi tạo và định nghĩa Info bên trong MainActivity. Chúng ta sẽ truyền Info vào.
class MainActivity(val info:Info) : AppCompatActivity()
Ồ! Có gì đó sai sai trong đoạn code trên. Dường như Android không cho phép truyền tham số vào MainActivity. Vậy phải làm sao đây?
Nếu bạn có kinh nghiệm lập trình Android thì bạn sẽ nghĩ đến class Application. Chúng ta sẽ truyền tham số cho MainActivity từ class Application. Tuy nhiên, ở đây chúng ta không làm thế, chúng ta sẽ sử dụng Dagger2.
🔥 Rất có ích cho bạn: Quy tắc viết Code Clean trong Android
#Ứng dụng của Dagger 2 trong Android
Với Dagger2, giờ đây chúng ta có thể làm cho Info xuất hiện một cách kì diệu.
Đầu tiên, chúng ta sẽ loại bỏ được đoạn code khai báo khởi tạo class Info: val info = Info();
Ngoài Dagger 2 có thể làm được điều này, thì chỉ có David Copperfield mới làm được 🙂
Thực ra, chúng ta vẫn phải truyền Info vào MainActivity nhưng không phải thông qua việc dùng hàm khởi tạo như trên.
Tạm thời, code sẽ như sau:
lateinit var info: Info override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) text_view.text = info.text } } class Info { val text = "Hello Dagger 2" }
Từ khóa lateinit
là khởi tạo sau. Chúng ta không muốn info là NULL, nó phải được khởi tạo và có giá trị.
Lưu ý: Đoạn code trên vẫn chưa chạy được. Nếu bạn thử chạy sẽ gặp lỗi CRASH vì đối tượng Info chưa được khởi tạo mà bạn lại gọi info.text
Đừng lo lắng, chúng ta sẽ thực sự bắt đầu với Dagger 2, xem nó khởi tạo Info như nào nhé!
#Thêm thư viện Dagger 2 trong Android Studio
Cũng giống như bất kì thư viện nào khác, bạn khai báo dagger 2 trong mục dependencies như sau:
dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) // ... other dependencies ... implementation 'com.google.dagger:dagger:2.13' kapt 'com.google.dagger:dagger-compiler:2.13' }
và
apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt'
Sau đó nhớ Sync lại dự án.
#Giới thiệu đôi chút về @ Component
Theo định nghĩa chính thức thì @component là 1 interface dùng để làm cầu nối giữa module và injection.
Trong bài viết này, mình muốn tạo một component mà cho phép mình tạo các biến info thành viên.
@Component interface MagicBox { fun poke(app: MainActivity) }
Để mình giải thích cho rõ ràng hơn chút. Bạn có thể tưởng tượng rằng MagicBox sẽ được sử dụng để poke MainActivity như hình minh họa bên dưới và cung cấp Info cho MainActivity.
Việc của chúng ta là đưa Info vào MagicBox, còn lại hãy để Dagger2 lo.
Mình tin rằng sẽ có nhiều bạn thắc mắc: Tại sao cứ cố tạo MagicBox với Dagger2 chi cho mệt, thay vì tạo luôn Info trực tiếp trong MainActivity như ban đầu ấy, có sao đâu?
Giả sử, chúng ta có rất nhiều member variable khác (ngoài biến info ra). Và nhiều Class hay Activity khác cũng cần đến chúng. Thế lúc đó các class đó cũng sẽ phải tự tạo các member variables tương tự của riêng mình.
Duplicate code là đây chứ đâu!
Bạn đã hiểu vai trò của MagicBox chưa!?
🔥 Đọc thêm: Chia sẻ mã nguồn Flashlight 3D Android giá 69$
#Đưa Info vào @Component…Giới thiệu @Inject
Okay, giờ chúng ta sẽ đưa Info vào @Component…
Mình đoán là một số bạn sẽ nghĩ nếu chúng ta tự đưa Info vào @Component thì @Component có gì đặc biệt đâu. Chúng ta cũng có thể tạo một class bình thường để làm điều đó mà.
Thực tế thì chúng ta không cần phải truyền Info vào MagicBox. Chúng ta chỉ cần thêm một annotation vào ngay trước class Info. Sau đó Dagger 2 sẽ tự biết phải làm gì.
Và annotation đó chính là @Inject.
class Info @Inject constructor() { val text = "Hello Dagger 2" }
Bạn có thể thêm @Inject vào bất cứ class/Object nào bạn muốn. Dagger2 sẽ kết nối class đó tới MagicBox.
#Xây dựng MagicBox
Có một điều đặc biệt là MagicBox được khai báo là một Interface. Về nguyên tắc, để sử dụng Interface, chúng ta cần phải implement nó.
Tuy nhiên, ai sẽ implement Interface? MainActivity chăng?
Không phải. Với Dagger2 trong Android, khi bạn đã khai báo MagicBox với annotation @Component, bạn chỉ cần gọi hàm trong MagicBox thông qua static class với tên là: DaggerMagicBox.
Kiểu như sau:
DaggerMagicBox.create().poke()
Tuy nhiên, chương trình vẫn CRASH!!!
Bởi vì dagger 2 trong android không được tạo bất kì biến thành viên (member variables) nào khi chưa được phép.
Vì vây, để thông báo cho Dagger 2 biết biến nào được phép tạo bằng cách thêm annotation @Inject vào trước biến đó là được.
@Inject lateinit var info: Info
Code hoàn chỉnh sẽ như sau:
class MainActivity : AppCompatActivity() { @Inject lateinit var info: Info override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) DaggerMagicBox.create().poke(this) text_view.text = info.text } } class Info @Inject constructor() { val text = "Hello Dagger 2" } @Component interface MagicBox { fun poke(app: MainActivity) }
Nói thì dông dài nhưng code thì có tý tẹo thôi ah.
#Tạm kết
Như vậy mình đã hướng dẫn các bạn tìm hiểu Dagger cùng với cách Dependency Injection với Dagger2 trong Android.
Các bạn có thể download toàn bộ code của bài viết tại đây:
Nếu bài viết có chỗ nào khó hiểu thì để lại bình luận để mọi người cùng hỗ trợ nhé!
Cám ơn bạn đã theo dõi!
Đọc thêm:
- Năm 2020 rồi, lập trình viên Android cần học những gì?
- Toàn tập về AlarmManager trong Android
- Sử dụng Data Binding trong Android hiệu quả
Nguồn: Medium
Bạn có code thuần java cho bài này không ạ
Chào bạn,
Cho mình hỏi nếu mình dùng Java thì “lateinit var Info” mình có thể thay bằng lệnh gì tương ứng nhỉ.
Mình cảm ơn
đây là bài dịch mà, đã từng đọc bài này bằng tiếng Anh
Thì trong bài viết có ghi nguồn mà bạn.
Thank bạn!
Bài viết của bạn rất hay!
Cám ơn bạn Tiến nhé
Bạn có thể viết thêm một vài bài về Dagger 2 nửa không ạ !
Xin cám ơn nhiều !
Hi Tâm,
Bạn muốn viết thêm chủ đề j về Dagger2, cụ thể thêm được không bạn?
Chào bạn !
Mình thắc mắc là với một ứng dụng sẽ có nhiều module khác nhau vậy thì làm sao để có những module con ạ !