Computer >> Máy Tính >  >> Hệ thống >> Android

Tất cả về kiến ​​trúc đó:khám phá các mẫu kiến ​​trúc khác nhau và cách sử dụng chúng trong ứng dụng của bạn

Loạt ứng dụng Kriptofolio - Phần 3

Điều quan trọng nhất cần tập trung khi bắt đầu xây dựng một ứng dụng mới là kiến ​​trúc. Sai lầm lớn nhất mà bạn có thể mắc phải là không có phong cách kiến ​​trúc nào cả.

Chủ đề về sự lựa chọn kiến ​​trúc đã gây ra khá nhiều tranh cãi đối với cộng đồng Android trong những năm gần đây. Ngay cả Google cũng quyết định tham gia. Vào năm 2017, họ đã đề xuất phương pháp tiếp cận kiến ​​trúc chuẩn hóa của riêng mình bằng cách phát hành Thành phần kiến ​​trúc Android. Nó nhằm giúp cuộc sống của các nhà phát triển dễ dàng hơn.

Trong bài đăng này, trước tiên tôi sẽ thảo luận về lý do tại sao chúng ta cần kiến ​​trúc ứng dụng của mình. Chúng tôi sẽ bao gồm những tùy chọn mà chúng tôi có. Sau đó, chúng ta sẽ học cách làm điều đó. Thay vì phát minh lại bánh xe, chúng tôi sẽ sử dụng các nguyên tắc do nhóm Android cung cấp.

Bài đăng này là bài khó nhất đối với tôi vì tôi thiếu kiến ​​thức về nó. Đầu tiên tôi phải nghiên cứu thật kỹ chủ đề kiến ​​trúc để có thể nhìn thấy bức tranh toàn cảnh hơn. Bây giờ tôi đã sẵn sàng chia sẻ những phát hiện của mình với bạn.

Nội dung sê-ri

  • Giới thiệu:Lộ trình xây dựng ứng dụng Android hiện đại trong năm 2018–2019
  • Phần 1:Giới thiệu về các nguyên tắc SOLID
  • Phần 2:Cách bắt đầu xây dựng ứng dụng Android của bạn:tạo Mockups, giao diện người dùng và bố cục XML
  • Phần 3:Tất cả về kiến ​​trúc đó:khám phá các mẫu kiến ​​trúc khác nhau và cách sử dụng chúng trong ứng dụng của bạn (bạn đang ở đây)
  • Phần 4:Cách triển khai Dependency Injection trong ứng dụng của bạn với Dagger 2
  • Phần 5:Xử lý các Dịch vụ Web RESTful bằng cách sử dụng Retrofit, OkHttp, Gson, Glide và Coroutines

Tại sao bạn nên quan tâm đến kiến ​​trúc ứng dụng?

Thông thường, khi bạn bắt đầu làm việc với Android, bạn sẽ viết hầu hết logic kinh doanh cốt lõi trong các hoạt động hoặc phân đoạn. Điều này xảy ra với tất cả các nhà phát triển Android mới, bao gồm cả tôi. Tất cả các hướng dẫn ngắn và tất cả các mẫu đề xuất làm điều đó. Và trên thực tế, đối với các ứng dụng nhỏ được tạo ra để giải thích, hoạt động đủ tốt.

Tuy nhiên, hãy cố gắng làm điều đó trên một ứng dụng thực luôn thay đổi theo nhu cầu của người dùng và mở rộng ứng dụng đó với các tính năng mới. Chẳng bao lâu bạn sẽ thấy rằng trải nghiệm viết mã của bạn ngày càng trở nên khó khăn hơn. Mọi thứ trở nên được quản lý bởi cái gọi là “Lớp học của Chúa” như các hoạt động hoặc các mảnh vỡ. Chúng có rất nhiều dòng mã khiến bạn dễ bị lạc.

Về cơ bản, tất cả mã của bạn bắt đầu trông giống như mì spaghetti, nơi mọi thứ được trộn lẫn với nhau. Tất cả các phần phụ thuộc vào nhau. Sau đó, khi doanh nghiệp yêu cầu những thay đổi mới, bạn không còn lựa chọn nào khác ngoài việc xây dựng lại toàn bộ dự án. Đó cũng là điểm mà các câu hỏi về kiến ​​trúc bắt đầu xuất hiện.

Có cách nào tốt hơn để cấu trúc mã của bạn không?

Tất nhiên là có! Chìa khóa để có mã chất lượng cao là tuân theo các nguyên tắc SOLID. Tôi đã nói về điều này trong bài viết trước của tôi (không phải không có lý do). Bạn cũng nên áp dụng một số mô hình kiến ​​trúc để tách biệt các mối quan tâm. Trên thực tế, mục tiêu cuối cùng của bạn nên tách rời các mối quan tâm. Đây là điểm quan trọng nhất cho biết chất lượng mã. Có khá nhiều mẫu cho các kiến ​​trúc ứng dụng. Được biết đến nhiều nhất là các kiến ​​trúc ba tầng cổ điển như:

  • MVC:Model-View-Controller
  • MVP:Người mẫu-Người xem-Người trình bày
  • MVVM:Model-View-ViewModel

Tất cả các mẫu này đại diện cho ý tưởng tương tự chính - để cấu trúc mã dự án của bạn theo cách nó được phân tách bằng các lớp chung khác nhau. Mỗi lớp đều có trách nhiệm riêng của nó. Đó là lý do tại sao dự án của bạn trở thành mô-đun:các phần mã tách biệt dễ kiểm tra hơn và ứng dụng của bạn đủ linh hoạt để thay đổi liên tục.

Nếu chúng ta nói về từng mẫu riêng lẻ, chủ đề sẽ trở nên quá rộng. Tôi chỉ giới thiệu cho bạn từng cái để bạn có thể hiểu được những điểm khác biệt chính.

Mẫu Model-View-Controller (MVC)

Mô hình này là kiến ​​trúc ứng dụng Android lặp lại đầu tiên được lấy lại từ thời xưa. Nó gợi ý rằng bạn nên tách mã của mình thành 3 lớp khác nhau:

Mô hình - lớp dữ liệu. Chịu trách nhiệm xử lý logic nghiệp vụ và giao tiếp với mạng và các lớp cơ sở dữ liệu.

View - lớp giao diện người dùng (UI). Đó là một hình dung đơn giản về dữ liệu từ Mô hình.

Bộ điều khiển - lớp logic, được thông báo về hành vi của người dùng và cập nhật Mô hình khi cần.

Tất cả về kiến ​​trúc đó:khám phá các mẫu kiến ​​trúc khác nhau và cách sử dụng chúng trong ứng dụng của bạn

Đây là lược đồ MVC. Trong đó, chúng ta có thể thấy rằng cả Controller và View đều phụ thuộc vào Model. Bộ điều khiển cập nhật dữ liệu. Chế độ xem lấy dữ liệu. Tuy nhiên, Mô hình được tách biệt và có thể được kiểm tra độc lập với giao diện người dùng.

Có một số cách tiếp cận về cách áp dụng mẫu MVC. Nó khá khó hiểu.

Một là khi các hoạt động và phân đoạn hoạt động giống như Bộ điều khiển. Họ chịu trách nhiệm xử lý dữ liệu và cập nhật lượt xem. Vấn đề với cách tiếp cận kiến ​​trúc này là các hoạt động và các mảnh có thể trở nên khá lớn và rất khó kiểm tra.

Một cách tiếp cận khác có vẻ hợp lý hơn (và đúng đắn) là nơi các hoạt động và phân đoạn sẽ là Chế độ xem trong thế giới MVC. Bộ điều khiển phải là các lớp riêng biệt không mở rộng hoặc sử dụng bất kỳ lớp Android nào. Tương tự cho các Mô hình.

Dù sao nếu bạn điều tra thêm về MVC, bạn sẽ phát hiện ra rằng khi được áp dụng cho một dự án Android, ngay cả theo cách chính xác, các lớp mã phụ thuộc vào nhau. Đó là lý do tại sao tôi không khuyên bạn sử dụng nó nữa cho ứng dụng Android tiếp theo của mình.

Mẫu Model-View-Presenter (MVP)

Sau cách tiếp cận đầu tiên không hiệu quả, các nhà phát triển Android đã chuyển sang và cố gắng sử dụng một trong những mẫu kiến ​​trúc phổ biến nhất - MVP. Mô hình này đại diện cho sự lựa chọn kiến ​​trúc lặp lại lần thứ hai. Mô hình này đã trở nên được sử dụng rộng rãi và vẫn là một mô hình được khuyến nghị. Đối với bất kỳ ai bắt đầu phát triển Android, nó rất dễ học. Chúng ta hãy xem xét 3 vai trò lớp riêng biệt của nó:

Mô hình - lớp dữ liệu, giống như trên mẫu MVC. Chịu trách nhiệm xử lý logic nghiệp vụ và giao tiếp với mạng và các lớp cơ sở dữ liệu.

View - lớp giao diện người dùng (UI). Hiển thị dữ liệu và thông báo cho Người trình bày về các hành động của người dùng.

Người trình bày - lấy dữ liệu từ Mô hình, áp dụng logic giao diện người dùng và quản lý trạng thái của Chế độ xem, quyết định hiển thị những gì và phản ứng với các thông báo đầu vào của người dùng từ Chế độ xem. Về cơ bản, đây là bộ điều khiển từ MVC ngoại trừ việc nó hoàn toàn không bị ràng buộc với Chế độ xem, chỉ là một giao diện.

Tất cả về kiến ​​trúc đó:khám phá các mẫu kiến ​​trúc khác nhau và cách sử dụng chúng trong ứng dụng của bạn

Lược đồ MVP cho thấy rằng Chế độ xem và Người trình bày có liên quan chặt chẽ với nhau. Họ cần phải có một tham chiếu đến nhau. Mối quan hệ của họ được xác định trong Contract lớp giao diện.

Mô hình này có một nhược điểm đáng kể nhưng có thể kiểm soát được. Người trình bày có xu hướng mở rộng đến một lớp người hiểu biết rộng lớn nếu bạn không đủ cẩn thận và không phá vỡ mã của mình theo nguyên tắc trách nhiệm duy nhất. Tuy nhiên, nói chung, mô hình MVP cung cấp sự tách biệt rất tốt các mối quan tâm. Nó có thể là sự lựa chọn chính của bạn cho dự án của bạn.

Mẫu Model-View-ViewModel (MVVM)

Mẫu MVVM là lần lặp lại thứ ba của cách tiếp cận. Nó đã trở thành mẫu kiến ​​trúc được nhóm Android đề xuất với bản phát hành Thành phần kiến ​​trúc Android. Đó là lý do tại sao chúng tôi sẽ tập trung vào việc tìm hiểu mô hình này hơn hết. Ngoài ra, tôi sẽ sử dụng nó cho ứng dụng “My Crypto Coins”. Như trước đây, chúng ta hãy xem xét các lớp mã riêng biệt của nó:

Mô hình - tóm tắt nguồn dữ liệu. ViewModel làm việc với Model để lấy và lưu dữ liệu.

Chế độ xem - thông báo cho ViewModel về các hành động của người dùng.

ViewModel - hiển thị các luồng dữ liệu có liên quan đến Chế độ xem.

Sự khác biệt so với mẫu MVP là, trong MVVM, ViewModel không giữ một tham chiếu đến View giống như với Presenter. Trong MVVM, ViewModel hiển thị một luồng sự kiện mà các Chế độ xem khác nhau có thể liên kết. Mặt khác, trong trường hợp MVP, Người trình bày trực tiếp cho Chế độ xem biết những gì sẽ hiển thị. Hãy xem lược đồ MVVM:

Tất cả về kiến ​​trúc đó:khám phá các mẫu kiến ​​trúc khác nhau và cách sử dụng chúng trong ứng dụng của bạn

Trong MVVM, View có tham chiếu đến ViewModel. ViewModel không có thông tin về View. Có một mối quan hệ nhiều-một giữa View và ViewModel.

So sánh MVC so với MVP và MVVM

Đây là bảng tổng hợp tất cả các mẫu mà tôi đã nói đến:

Tất cả về kiến ​​trúc đó:khám phá các mẫu kiến ​​trúc khác nhau và cách sử dụng chúng trong ứng dụng của bạn

Như bạn có thể nhận thấy, MVC không quá tốt so với MVP và MVVM khi xây dựng một ứng dụng hiện đại có thể kiểm tra và mô-đun. Nhưng mỗi mẫu đều có những ưu nhược điểm riêng. Đó là một lựa chọn tốt nếu nó chính xác phù hợp với nhu cầu của bạn. Tôi khuyên bạn nên điều tra và tìm hiểu thêm về tất cả các mẫu này vì nó đáng giá.

Trong thời gian chờ đợi, tôi sẽ tiếp tục dự án của mình với xu hướng trong năm 2018, cũng được thúc đẩy bởi Google - MVVM.

Thành phần kiến ​​trúc Android

Nếu bạn đã quen với vòng đời của ứng dụng Android, bạn sẽ biết bạn phải đau đầu thế nào khi xây dựng một ứng dụng tránh tất cả các vấn đề về luồng dữ liệu cũng như các vấn đề về độ bền và ổn định thường xuất hiện trong quá trình thay đổi cấu hình.

Vào năm 2017, nhóm Android quyết định rằng chúng tôi đã phải vật lộn đủ nhiều. Họ nhận trách nhiệm và giới thiệu khung Thành phần Kiến trúc Android. Điều này cuối cùng cho phép bạn giải quyết tất cả những vấn đề này mà không làm phức tạp mã của bạn hoặc thậm chí áp dụng các biện pháp hack cho nó.

Thành phần Kiến trúc Android là một tập hợp các thư viện giúp bạn thiết kế các ứng dụng mạnh mẽ, có thể kiểm tra và bảo trì được. Tại thời điểm hiện tại khi tôi viết bài đăng trên blog này, nó bao gồm các thành phần sau:

  • Liên kết dữ liệu - ràng buộc một cách khai báo dữ liệu quan sát được với các phần tử giao diện người dùng
  • Vòng đời - quản lý hoạt động của bạn và phân mảnh các vòng đời
  • LiveData - thông báo các lượt xem khi cơ sở dữ liệu cơ bản thay đổi
  • Điều hướng - xử lý mọi thứ cần thiết để điều hướng trong ứng dụng
  • Phân trang - tải dần thông tin theo yêu cầu từ nguồn dữ liệu của bạn
  • Phòng - truy cập cơ sở dữ liệu SQLite thông thạo
  • ViewModel - quản lý dữ liệu liên quan đến giao diện người dùng theo cách có ý thức về vòng đời
  • WorkManager - quản lý các công việc nền Android của bạn

Với sự trợ giúp của Thành phần kiến ​​trúc Android, chúng tôi sẽ triển khai mẫu kiến ​​trúc MVVM trong ứng dụng My Crypto Coins theo sơ đồ sau:

Tất cả về kiến ​​trúc đó:khám phá các mẫu kiến ​​trúc khác nhau và cách sử dụng chúng trong ứng dụng của bạn

Đó là một kiến ​​trúc được đề xuất bởi Google. Nó cho thấy tất cả các mô-đun sẽ tương tác với nhau như thế nào. Tiếp theo, chúng tôi sẽ chỉ đề cập đến các Thành phần kiến ​​trúc Android cụ thể mà chúng tôi sẽ sử dụng trong dự án của mình.

Tổ chức các tệp nguồn của bạn

Trước khi bắt đầu phát triển, chúng ta nên cân nhắc cách tổ chức các tệp nguồn của dự án. Chúng ta không thể để câu hỏi này không được trả lời, vì sau này chúng ta sẽ có một cấu trúc lộn xộn khó hiểu và khó sửa đổi.

Có một số cách để làm điều đó. Một là tổ chức theo danh mục thành phần. Ví dụ:tất cả các hoạt động đi vào thư mục riêng của chúng, tất cả các bộ điều hợp sẽ đi đến thư mục của chúng, v.v.

Một cách khác là sắp xếp mọi thứ theo các tính năng của ứng dụng. Ví dụ:tính năng tìm kiếm và thêm tiền điện tử trong danh sách tất cả các loại tiền điện tử sẽ chuyển đến addsearchlist của riêng nó thư mục. Ý tưởng chính là bạn cần thực hiện theo một cách cụ thể nào đó thay vì đặt mọi thứ một cách ngẫu nhiên. Tôi sử dụng một số loại kết hợp của cả hai.

Tất cả về kiến ​​trúc đó:khám phá các mẫu kiến ​​trúc khác nhau và cách sử dụng chúng trong ứng dụng của bạn
Cấu trúc thư mục ứng dụng Crypto Coins của tôi

Bên cạnh cấu trúc thư mục của dự án, bạn nên cân nhắc áp dụng một số quy tắc để đặt tên tệp dự án. Ví dụ:khi đặt tên cho các lớp Android, bạn nên xác định rõ mục đích của lớp trong tên.

ViewModel

Để bắt đầu phát triển kiến ​​trúc ứng dụng của chúng tôi, trước tiên chúng tôi sẽ tạo ViewModel. Mô hình chế độ xem là các đối tượng cung cấp dữ liệu cho các thành phần giao diện người dùng và tồn tại các thay đổi cấu hình.

Bạn có thể sử dụng ViewModel để giữ lại dữ liệu trong toàn bộ vòng đời của một hoạt động hoặc một phân đoạn. Các hoạt động và mảnh vỡ là những vật thể tồn tại trong thời gian ngắn. Chúng được tạo và hủy thường xuyên khi người dùng tương tác với một ứng dụng. ViewModel cũng phù hợp hơn để quản lý các tác vụ liên quan đến giao tiếp mạng, cũng như thao tác và tính bền bỉ của dữ liệu.

Như ví dụ bây giờ, hãy tạo một ViewModel cho MainListFragment để tách dữ liệu giao diện người dùng khỏi nó.

class MainViewModel : ViewModel() {
    ...
}

Sau đó, lấy ViewModel với một dòng mã.

class MainListFragment : Fragment() {
    ...
    private lateinit var viewModel: MainViewModel
    ...
    override fun onActivityCreated(savedInstanceState: Bundle?) {

        super.onActivityCreated(savedInstanceState)

        setupList()

        // Obtain ViewModel from ViewModelProviders, using this fragment as LifecycleOwner.
        viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
        ...
    }
    ...
}

Về cơ bản là vậy, xin chúc mừng! ? Hãy tiếp tục.

Dữ liệu trực tiếp

LiveData là một lớp lưu trữ dữ liệu có thể quan sát được. Nó tuân theo mô hình người quan sát. LiveData nhận biết vòng đời. Điều này có nghĩa là nó chỉ cập nhật trình quan sát thành phần ứng dụng (hoạt động, phân đoạn, v.v.) đang ở trạng thái vòng đời hoạt động.

Lớp LiveData trả về giá trị mới nhất của dữ liệu. Khi dữ liệu thay đổi, nó trả về giá trị được cập nhật. LiveData phù hợp nhất với ViewModel.

Chúng tôi sẽ sử dụng LiveData cùng với ViewModel như sau:

...
class MainViewModel : ViewModel() {

    private val liveData = MutableLiveData<ArrayList<Cryptocurrency>>()
    val data: LiveData<ArrayList<Cryptocurrency>>
        get() = liveData

    init {
        val tempData = ArrayList<Cryptocurrency>()

        val btc:Cryptocurrency = Cryptocurrency("Bitcoin", 1, 0.56822348, "BTC", 8328.77, 4732.60, 0.19, -10.60, 0.44, 20.82)
        val eth:Cryptocurrency = Cryptocurrency("Etherium", 2, 6.0, "ETH", 702.99, 4217.94, 0.13, -7.38, 0.79, 33.32)

        tempData.add(btc)
        tempData.add(eth)

        liveData.value = tempData
    }
}

Quan sát dữ liệu trên ViewModel, được hiển thị dưới dạng LiveData:

...
class MainListFragment : Fragment() {

    private lateinit var recyclerView: RecyclerView
    private lateinit var recyclerAdapter: MainRecyclerViewAdapter

    private lateinit var viewModel: MainViewModel

    ...

    override fun onActivityCreated(savedInstanceState: Bundle?) {

        super.onActivityCreated(savedInstanceState)

        setupList()

        // Obtain ViewModel from ViewModelProviders, using this fragment as LifecycleOwner.
        viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)

        // Observe data on the ViewModel, exposed as a LiveData
        viewModel.data.observe(this, Observer { data ->
            // Set the data exposed by the LiveData
            if (data != null) {
                recyclerAdapter.setData(data)
            }
        })
    }
    ...
}

Duyệt qua kho lưu trữ tại thời điểm này trong lịch sử ở đây.

Data Binding

Thư viện liên kết dữ liệu được tạo để loại bỏ mã soạn sẵn cần thiết để kết nối với bố cục XML.

Để sử dụng Data Binding trong các dự án Kotlin của mình, bạn cần bật hỗ trợ cho bộ xử lý chú thích bằng plugin trình biên dịch kapt. Đồng thời thêm khối liên kết dữ liệu vào tệp gradle cấu hình Android:

...
apply plugin: 'kotlin-kapt'

android {
    ...
    dataBinding {
        enabled = true
    }
}
...

Để sử dụng các lớp được tạo liên kết dữ liệu, chúng ta cần đặt tất cả mã chế độ xem trong <layo thẻ ut>. Khái niệm mạnh mẽ nhất về liên kết dữ liệu là chúng ta có thể liên kết một số lớp dữ liệu với bố cục xml và thuộc tính mục với các trường trực tiếp.

<layout xmlns:app="https://schemas.android.com/apk/res-auto"
    xmlns:tools="https://schemas.android.com/tools">

    <data>

        <variable
            name="cryptocurrency"
            type="com.baruckis.mycryptocoins.data.Cryptocurrency" />
    </data>
  
    ...      

            <android.support.v7.widget.AppCompatTextView
                android:id="@+id/item_name"
                style="@style/MainListItemPrimeText"
                android:layout_marginEnd="@dimen/main_cardview_list_item_text_between_margin"
                android:layout_marginStart="@dimen/main_cardview_list_item_inner_margin"
                android:text="@{cryptocurrency.name}"
                android:textAlignment="viewStart"
                app:layout_constraintBottom_toTopOf="@+id/item_amount_symbol"
                app:layout_constraintEnd_toStartOf="@+id/guideline1_percent"
                app:layout_constraintStart_toEndOf="@+id/item_image_icon"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintVertical_chainStyle="spread"
                tools:text="@string/sample_text_item_name" />

     ...
</layout>

Bộ điều hợp RecyclerView với phân chia dữ liệu sẽ trông như thế này:

class MainRecyclerViewAdapter() : RecyclerView.Adapter<MainRecyclerViewAdapter.BindingViewHolder>() {

    private lateinit var dataList: ArrayList<Cryptocurrency>

    fun setData(newDataList: ArrayList<Cryptocurrency>) {
        dataList = newDataList
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val binding = FragmentMainListItemBinding.inflate(inflater, parent, false)

        return BindingViewHolder(binding)
    }

    override fun onBindViewHolder(holder: BindingViewHolder, position: Int) = holder.bind(dataList[position])

    override fun getItemCount(): Int = dataList.size

    ...

    inner class BindingViewHolder(var binding: FragmentMainListItemBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(cryptocurrency: Cryptocurrency) {
            binding.cryptocurrency = cryptocurrency

            binding.itemRanking.text = String.format("${cryptocurrency.rank}")
            ...
            binding.executePendingBindings()
        }
    }
}

Cuối cùng thì không viết nữa findViewById ? Duyệt qua kho lưu trữ tại thời điểm này trong lịch sử ở đây.

Phòng

Ứng dụng của chúng tôi cần lưu trữ dữ liệu liên tục của các loại tiền điện tử khác nhau mà người dùng nắm giữ. Điều này sẽ được lưu trữ bên trong cơ sở dữ liệu cục bộ được lưu giữ riêng tư bên trong thiết bị Android.

Để lưu trữ dữ liệu có cấu trúc trong cơ sở dữ liệu riêng tư, chúng ta sẽ sử dụng cơ sở dữ liệu SQLite. Đây thường là lựa chọn tốt nhất.

Để tạo cơ sở dữ liệu SQLite cho ứng dụng của mình, chúng tôi sẽ sử dụng Room. Room là một thư viện bền vững do nhóm Android tạo ra, nó là một trình bao bọc bên trên SQLite. Nó là một lớp trừu tượng loại bỏ phần lớn mã soạn sẵn mà bạn cần để tương tác với SQLite. Nó cũng bổ sung kiểm tra thời gian biên dịch các truy vấn SQL của bạn.

Cách tốt nhất để nghĩ về nó là một công cụ ORM (Object Relational Mapper) được thiết kế để tự động tạo mã keo để ánh xạ giữa các phiên bản đối tượng và các hàng trong cơ sở dữ liệu của bạn.

Về cơ bản, có 3 thành phần chính trong Room:

  1. Thực thể - thành phần này đại diện cho một lớp chứa một hàng cơ sở dữ liệu. Đối với mỗi thực thể, một bảng cơ sở dữ liệu được tạo để chứa các mục.
  2. DAO (Đối tượng truy cập dữ liệu) - thành phần chính chịu trách nhiệm xác định các phương thức truy cập cơ sở dữ liệu.
  3. Cơ sở dữ liệu - một thành phần là lớp chủ sử dụng chú thích để xác định danh sách các thực thể, danh sách các DAO và phiên bản cơ sở dữ liệu và đóng vai trò là điểm truy cập chính cho kết nối cơ bản.
Tất cả về kiến ​​trúc đó:khám phá các mẫu kiến ​​trúc khác nhau và cách sử dụng chúng trong ứng dụng của bạn

Hãy làm theo các bước đơn giản sau để thiết lập Phòng trong ứng dụng My Crypto Coins của chúng tôi:

  1. Tạo một thực thể.
@Entity
data class Cryptocurrency(val name: String,
                          val rank: Short,
                          val amount: Double,
                          @PrimaryKey
                          val symbol: String,
                          val price: Double,
                          val amountFiat: Double,
                          val pricePercentChange1h: Double,
                          val pricePercentChange7d: Double,
                          val pricePercentChange24h: Double,
                          val amountFiatChange24h: Double)

Thêm một số thông tin bổ sung để cho Room biết về cấu trúc của nó trong cơ sở dữ liệu.

2. Tạo DAO.

@Dao
interface MyCryptocurrencyDao {

    @Query("SELECT * FROM Cryptocurrency")
    fun getMyCryptocurrencyLiveDataList(): LiveData<List<Cryptocurrency>>

    @Insert
    fun insertDataToMyCryptocurrencyList(data: List<Cryptocurrency>)
}

Để bắt đầu, chúng tôi sẽ tạo một DAO chỉ cho phép chúng tôi truy xuất các bản ghi từ bảng mà chúng tôi đã tạo với Thực thể và cũng để chèn một số dữ liệu mẫu.

3. Tạo và thiết lập Cơ sở dữ liệu.

Điều quan trọng cần nói là cá thể Cơ sở dữ liệu lý tưởng chỉ nên được xây dựng một lần cho mỗi phiên. Một cách để đạt được điều này là sử dụng mẫu Singleton.

@Database(entities = [Cryptocurrency::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {

    abstract fun myCryptocurrencyDao(): MyCryptocurrencyDao


    // The AppDatabase a singleton to prevent having multiple instances of the database opened at the same time.
    companion object {

        // Marks the JVM backing field of the annotated property as volatile, meaning that writes to this field are immediately made visible to other threads.
        @Volatile
        private var instance: AppDatabase? = null

        // For Singleton instantiation.
        fun getInstance(context: Context): AppDatabase {
            return instance ?: synchronized(this) {
                instance ?: buildDatabase(context).also { instance = it }
            }
        }

        // Creates and pre-populates the database.
        private fun buildDatabase(context: Context): AppDatabase {
            return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME)
                    // Prepopulate the database after onCreate was called.
                    .addCallback(object : Callback() {
                        override fun onCreate(db: SupportSQLiteDatabase) {
                            super.onCreate(db)
                            // Insert the data on the IO Thread.
                            ioThread {
                                getInstance(context).myCryptocurrencyDao().insertDataToMyCryptocurrencyList(PREPOPULATE_DATA)
                            }
                        }
                    })
                    .build()
        }

        // Sample data.
        val btc: Cryptocurrency = Cryptocurrency("Bitcoin", 1, 0.56822348, "BTC", 8328.77, 4732.60, 0.19, -10.60, 0.44, 20.82)
        val eth: Cryptocurrency = Cryptocurrency("Etherium", 2, 6.0, "ETH", 702.99, 4217.94, 0.13, -7.38, 0.79, 33.32)

        val PREPOPULATE_DATA = listOf(btc, eth)

    }

}
private val IO_EXECUTOR = Executors.newSingleThreadExecutor()

// Utility method to run blocks on a dedicated background thread, used for io/database work.
fun ioThread(f : () -> Unit) {
    IO_EXECUTOR.execute(f)
}

Như bạn thấy trong lần chạy đầu tiên, cơ sở dữ liệu sẽ được tích hợp sẵn một số dữ liệu mẫu chỉ dành cho mục đích thử nghiệm.

4. Bước EXTRA. Tạo Kho lưu trữ.

Kho lưu trữ không phải là một phần của thư viện Thành phần Kiến trúc. Đây là phương pháp hay nhất được đề xuất để phân tách và kiến ​​trúc mã.

Tất cả về kiến ​​trúc đó:khám phá các mẫu kiến ​​trúc khác nhau và cách sử dụng chúng trong ứng dụng của bạn

Nó là nguồn trung thực duy nhất cho tất cả dữ liệu ứng dụng trong trường hợp bạn phải quản lý nhiều nguồn dữ liệu.

class MyCryptocurrencyRepository private constructor(
        private val myCryptocurrencyDao: MyCryptocurrencyDao
) {

    fun getMyCryptocurrencyLiveDataList(): LiveData<List<Cryptocurrency>> {
        return myCryptocurrencyDao.getMyCryptocurrencyLiveDataList()
    }

    companion object {

        // Marks the JVM backing field of the annotated property as volatile, meaning that writes to this field are immediately made visible to other threads.
        @Volatile
        private var instance: MyCryptocurrencyRepository? = null

        // For Singleton instantiation.
        fun getInstance(myCryptocurrencyDao: MyCryptocurrencyDao) =
                instance ?: synchronized(this) {
                    instance
                            ?: MyCryptocurrencyRepository(myCryptocurrencyDao).also { instance = it }
                }
    }
}

Chúng tôi sẽ sử dụng kho lưu trữ này trong ViewModel của chúng tôi.

class MainViewModel(myCryptocurrencyRepository: MyCryptocurrencyRepository) : ViewModel() {

    val liveData = myCryptocurrencyRepository.getMyCryptocurrencyLiveDataList()
}

Mã Fragment của chúng tôi cũng phát triển.

class MainListFragment : Fragment() {

    ...

    private lateinit var viewModel: MainViewModel

    ...

    override fun onActivityCreated(savedInstanceState: Bundle?) {

        super.onActivityCreated(savedInstanceState)

        setupList()
        subscribeUi()
    }

    ...

    private fun subscribeUi() {

        val factory = InjectorUtils.provideMainViewModelFactory(requireContext())
        // Obtain ViewModel from ViewModelProviders, using this fragment as LifecycleOwner.
        viewModel = ViewModelProviders.of(this, factory).get(MainViewModel::class.java)

        // Update the list when the data changes by observing data on the ViewModel, exposed as a LiveData.
        viewModel.liveData.observe(this, Observer<List<Cryptocurrency>> { data ->
            if (data != null && data.isNotEmpty()) {
                emptyListView.visibility = View.GONE
                recyclerView.visibility = View.VISIBLE
                recyclerAdapter.setData(data)
            } else {
                recyclerView.visibility = View.GONE
                emptyListView.visibility = View.VISIBLE
            }
        })

    }

}

Bởi vì lớp ViewModel của chúng ta bây giờ có một hàm tạo không còn trống nữa, chúng ta cần triển khai một mẫu nhà cung cấp. Điều này sẽ được chuyển đến ViewModelProviders.of() phương thức làm tham số thứ hai.

object InjectorUtils {

    fun provideMainViewModelFactory(
            context: Context
    ): MainViewModelFactory {
        val repository = getMyCryptocurrencyRepository(context)
        return MainViewModelFactory(repository)
    }

    private fun getMyCryptocurrencyRepository(context: Context): MyCryptocurrencyRepository {
        return MyCryptocurrencyRepository.getInstance(
                AppDatabase.getInstance(context).myCryptocurrencyDao())
    }
}
class MainViewModelFactory(private val repository: MyCryptocurrencyRepository) : ViewModelProvider.NewInstanceFactory() {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return MainViewModel(repository) as T
    }
}

Duyệt qua kho lưu trữ tại thời điểm này trong lịch sử ở đây.

Suy nghĩ cuối cùng

Kiến trúc thiết kế, mà chúng ta đã thảo luận trong phần này, nên được sử dụng như các hướng dẫn thông tin nhưng không phải là một quy tắc cứng. Tôi không muốn đi quá nhiều chi tiết vào từng chủ đề. Với Thành phần kiến ​​trúc Android, chúng tôi đã xem xét quá trình viết mã. Hãy nhớ rằng còn nhiều điều cần tìm hiểu về từng thành phần riêng lẻ và tôi khuyên bạn nên làm điều đó.

Hãy tóm tắt mọi thứ mà chúng tôi quản lý để thực hiện:

  • Trong ứng dụng My Crypto Coins, mỗi màn hình riêng biệt đều có ViewModel riêng. Điều này sẽ tồn tại sau bất kỳ thay đổi cấu hình nào và bảo vệ người dùng khỏi mọi mất mát dữ liệu.
  • Giao diện người dùng của Ứng dụng là một loại phản ứng. Điều này có nghĩa là nó sẽ cập nhật ngay lập tức khi dữ liệu thay đổi trong back-end. Điều đó được thực hiện với sự trợ giúp của LiveData.
  • Dự án của chúng tôi có ít mã hơn vì chúng tôi liên kết trực tiếp với các biến trong mã của mình bằng cách sử dụng Data Binding.
  • Cuối cùng, ứng dụng của chúng tôi lưu trữ cục bộ dữ liệu người dùng bên trong thiết bị dưới dạng cơ sở dữ liệu SQLite. Cơ sở dữ liệu được tạo thuận tiện với thành phần Phòng. Mã của Ứng dụng được cấu trúc theo các tính năng và tất cả kiến ​​trúc dự án là MVVM - một mẫu được nhóm Android đề xuất.

Kho lưu trữ

Bây giờ, như bạn đang thấy ứng dụng “Kriptofolio” (trước đây là “My Crypto Coins”) đang thực sự bắt đầu hình thành. Với cam kết kho lưu trữ mới nhất cho phần 3 này, bạn có thể thấy nó hiển thị độc đáo dữ liệu cơ sở dữ liệu đã được điều chỉnh trước cho người dùng với tổng giá trị danh mục đầu tư nắm giữ được tính toán chính xác.

Xem nguồn trên GitHub

Ačiū! Cảm ơn vì đã đọc! Ban đầu tôi đã xuất bản bài đăng này cho blog cá nhân của mình www.baruckis.com vào ngày 22 tháng 8 năm 2018.