Rss

7 thg 12, 2020

Tìm hiểu mô hình MVVM trong Android thông qua ví dụ - Phần 4 (cuối cùng): View

 Sau 3 phần trước đây

Thì hôm nay, chúng ta sẽ đi đến phần cuối cùng trong loạt bài Tìm hiểu về MVVM trong Android thông qua ví dụ này, đó chính là tìm hiểu component còn lại cuối cùng trong mô hình - View.

Nếu bạn vẫn chưa đọc các phần trước thì hãy tham khảo những link ở phía trên, nó sẽ giúp bạn dễ dàng hiểu được những phần liên quan trong bài biết hôm nay.

Cùng bắt đầu nào - View trong MVVM là gì?

View trong MVVM đơn giản là một thể hiện của Giao diện người dùng, chứa các thành phần hiển thị đến người dùng và không chứa bắt kỳ logic nào trong việc xử lý. Nó được móc xích với ViewModel và sẽ được cập nhật thông qua ViewModel.

Như đã giới thiệu ở phần 1 thì trong bài này, chúng ta sẽ áp dụng Data Binding vào việc xây dựng View của chúng ta.

Bắt đầu sẽ là activity_game.xml:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="gameViewModel"
            type="android.leo.tic_tac_toemvvm.viewmodel.game.GameViewModel" />
    </data>

    <GridLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="32dp"
        android:rowCount="3"
        android:columnCount="3">

        <ImageView
            android:id="@+id/cell_00"
            style="@style/Base.TextAppearance.AppCompat.Button"
            android:onClick="@{() -> gameViewModel.onClickedAtCell(0, 0)}"
            app:imageResource='@{gameViewModel.cells["00"]}'
            android:layout_column="0"
            android:layout_columnWeight="1"
            android:layout_row="0"
            android:layout_rowWeight="1"
            android:background="@drawable/cell_background" />

        ...
    </GridLayout>

</layout>

Có 2 thứ bạn cần chú ý trong file xml phía trên:

  • Bạn chỉ thấy 1 ImageView đại diện cho 1 cell trên bàn chơi, thực ra thì các cell còn lại cũng tương tự như vậy và bạn có thể copy ra dễ dàng. Chỉ cần chú ý chỗ cái id và app:imageResource là được (giá trị 00 trong @{gameViewModel.cells["00"]} và gameViewModel.onClickedAtCell(0, 0) chính là vị trí row column của bạn trong bàn chơi, hãy thay thế nó bởi các giá trị tương ứng). Mỗi khi một cell được click, mỗi giá trị hiển thị của nó sẽ được tạo ra tại ViewModel
  • Giá trị của biến name gameViewModel là một loại của GameViewModel, nó sẽ được sử dụng ở Activity để gọi các xử lý ở ViewModel

Phần phía trên là dành cho layout, chúng ta hãy cũng đi qua xem Activity của nó nhé.

Acitivty sẽ xây dựng ra sao?

Như đã nói ở phần 3, View sẽ nhận thông báo khi nào kết thúc game thông qua LiveData, cái được cung cấp bởi ViewModel của chúng ta đã xây dựng. Dưới đây là các việc mà View sẽ phải làm:

  • Tạo ra 2 người chơi bằng cách truyền tên họ đến ViewModel
  • Đăng ký và móc đến ViewModel
  • Phản ứng các thông báo từ ViewModel

Đây chính là code GameActivity của chúng ta:

public class GameActivity extends AppCompatActivity {

    private static final String NO_WINNER = "No One";

    private GameViewModel gameViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initDataBinding();
    }

    public void initDataBinding() {
        ActivityGameBinding activityGameBinding =
                DataBindingUtil.setContentView(this, R.layout.activity_game);
        gameViewModel = ViewModelProviders.of(this).get(GameViewModel.class);
        gameViewModel.init("P1", "P2");
        activityGameBinding.setGameViewModel(gameViewModel);
        setUpOnGameEndListener();
    }

    public void setUpOnGameEndListener() {
        gameViewModel.getWinner().observe(this, new Observer<Player>() {
            @Override
            public void onChanged(@Nullable Player player) {
                onGameWinnerChange(player);
            }
        });
    }

    @VisibleForTesting
    public void onGameWinnerChange(Player winner) {
        String winnerName = (winner != null &&
                (winner.name != null && !winner.name.isEmpty())) ? winner.name : NO_WINNER;
        Toast.makeText(this, "Winner is " + winnerName, Toast.LENGTH_SHORT).show();
    }
}

Một vài thứ bạn cần quan tâm trong đoạn code trên:

  • gameViewModel.getWinner().observe(...): Hàm này sẽ quan sát về người chơi chiến thắng thông qua LiveData ở ViewModel. Khi kết thúc game, giá trị của winner sẽ thay đổi, từ đó sẽ dẫn đến hàm onGameWinnerChange(player)
  • ActivityGameBinding: Lớp này được tự động tạo ra thông qua file xml layout activity_game. Nó giữ tất cả các thuộc tính ở xml file, trường hợp này chúng ta có biến gameViewModel như đã nói ở trên.
  • setGameViewModel: Hàm này sẽ gán gameViewModel của chúng ta đến gameViewModel trên layout đã tạo

Lời kết

Cảm ơn và chúc mừng bạn nếu bạn đã theo đến bài viết này (một chặn đường khá xa), hi vọng với những phần mình đã chia sẻ sẽ giúp các bạn hiểu hơn về mô hình MVVM kết hợp với Data Binding trong Android.

Nếu có gì chưa rõ, hãy tham khảo demo của mình ở link github này: Demo on github.

Chào tạm biệt và hẹn gặp lại trong các bài viết sau nhé! ^.^

Tìm hiểu mô hình MVVM trong Android thông qua ví dụ - Phần 3: ViewModel

 Chào các bạn, lại là mình đây 😄, thông qua 2 phần trước đây phần 1  phần 2 thì chúng ta đã cùng nhau tìm hiểu lý thuyết cũng như thành phần đầu tiên trong mô hình MVVM  Model. Trong phần tiếp theo này, chúng ta sẽ cùng tìm hiểu thành phần kế tiếp đó chính là ViewModel nha.

Vậy ViewModel trong MVVM là gì? Nó được xây dựng như thế nào? Chúng ta hãy cùng nhau tìm hiểu nhé !! ^.^

ViewModel là gì?

Như chúng ta đã nói ở phần trước, ViewModel trong MVVM sẽ chứa các model và quan sát các View của chúng ta. Nó không có ràng buộc với View, điều đó có nghĩa là bạn sẽ không tìm được bất kỳ instance nào của View trong ViewModel class cả. Nhưng nếu là vậy, nó sẽ giao tiếp cũng như cập nhập với View của chúng ta như thế nào nhỉ?

Thật ra có rất nhiều cách để làm điều đó, bạn có thể làm nó với eventBusRxAndroid, hoặc là giống trong ví dụ của chúng ta ở bài này, là sử dụng Google's ViewModel, một thành phần kiến trúc đi kèm vời LiveData

LiveData là như nào?

Bạn hãy nghĩa đơn giản thế này, LiveData là một thành phần bao ngoài một dữ liệu nào đó trong ứng dụng của bạn mà dữ liệu này rất hay bị thay đổi (ví dụ như số lượt like status của bạn trên Facebook, nó có thể được bao bọc trong LiveData vì nó rất hay thay đổi theo thời gian mà, mấy phút thôi đã lên mấy K đối với mấy bạn hotface rồi 😄).

Cái lớp bao ngoài này sẽ cung cấp dữ liệu cho tất cả đối tượng nào mà lắng nghe nó, ví dụ chúng ta có 1 chú chó Husky đáng yêu và nó được bao bọc bởi LiveData và chúng ta chính là đối tượng quan sát nó. Khi chú chó Husky đáng yêu đó có bắt kỳ hành động nào (chạy, nằm, ngáo,...) thì lập tức chúng ta sẽ có thể ghi nhận lại hành động (dữ liệu cuối cùng) mà chú chó Husky của chúng ta đã làm. Mô hình LiveData cũng tương tự như vậy, khi data có sự thay đổi thì đối tượng quan sát sẽ nhận được giá trị cuối cùng cho sự thay đổi đó.

Điều tuyệt vời của LiveData đó là nằm ở vòng đời của nó, như các bạn biết mỗi activity, fragment đều có vòng đời của riêng nó, từ lúc khởi tạo, hiển thị lên cho lúc bị hủy đi, thì LiveData sẽ chỉ cập nhập đến đối tượng quan sát đang trong vòng đời hoạt động mà thôi. Như thế thật tốt phải không nào!!

Trở lại với ViewModel của chúng ta

Nếu các bạn đã đọc qua phần trên, thì hẵn các bạn cũng đoán được là ViewModel của chúng ta sẽ chứa một đối tượng LiveData, cái sẽ quan sát View của chúng ta. Nói cách khác, nó sẽ thông báo đến View biết về các trạng thái khi nào Game end, điều đó cho phép View cập nhật lại UI của mình.

Cùng xem qua ViewModel của chúng ta nào:

public class GameViewModel extends ViewModel {

    public ObservableArrayMap<String, Integer> cells;
    private Game game;

    public void init(String p1, String p2) {
        game = new Game(p1, p2);
        cells = new ObservableArrayMap<>();
    }

    public void onClickedAtCell(int r, int c) {
        if (game.cells[r][c] == null) {
            game.cells[r][c] = new Cell(game.currentPlayer);
            int res = 0;
            if (game.currentPlayer.value == Player.PlayerValue.VALUE_X) {
                res = R.drawable.ic_cat_smile;
            } else {
                res = R.drawable.ic_rat;
            }
            cells.put(String.valueOf(r) + String.valueOf(c), res);

            if (game.isGameEnded()) {
                resetGame();
            } else {
                game.switchPlayer();
            }
        }
    }

    public LiveData<Player> getWinner() {
        return game.winners;
    }

    public void resetGame() {
        final Handler handler = new Handler(Looper.getMainLooper());
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                game.reset();
                cells.clear();
            }
        }, 1000);
    }
}

Để sử dụng được ObservableArrayMap các bạn hãy thêm DataBinding vào project của mình nha.

Có 2 thứ bạn cần chú ý trong đoạn code trên là:

  • onClickedAtCell(...): Hàm này, các bạn sẽ gặp nó trong phần sau khi nói về View, mục đích của nó là sẽ cập nhật lại giá trị cho cái cell mà chúng ta sẽ click vào khi chơi game.
  • getWinner(): Là một đối tượng LiveData được quan sát, nó sẽ biểu thị khi nào game chúng ta kết thúc để View có các xử lý phù hợp

Như vậy, chúng ta đã xây dựng xong ViewModel trong mô hình MVVM rồi, ở phần sau chúng ta sẽ cùng nhau xây dựng đối tượng cuối cùng là View và xem cách mà tạo ra ViewModel sẽ giao tiếp với View như thế nào nhé.

Cảm ơn các bạn đã theo dõi, hẹn gặp các bạn ở phần sau nha !! ^..^

Tìm hiểu mô hình MVVM trong Android thông qua ví dụ - Phần 2: Model

 

Chào các bạn

Là mình đây, như mình đã giới thiệu về MVVM trong Android ở phần trước thì trong MVVM có 3 thành phần là Model - View - ViewModel. Ở phần 2 này chúng ta sẽ cùng nhau tìm hiểu thành phần thứ nhất là Model nhé. Chúng ta sẽ cùng xây dựng model của một game quen thuộc như đã nói ở phần trước là game Tic-Tac-Toe.

Model trong MVVM không giống như Model trong MVP, trong MVP thì Model sẽ chịu trách nhiệm chứa các dữ liệu của đối tượng, logic sẽ do Presenter xử lý và gửi về cho View. Còn Model trong MVVM ngoài chứa thông tin của đổi tượng, nó sẽ bao gồm luôn các xử lý logic liên quan đến đối tượng đó (giống với Model trong MVC vậy đó).

Bắt đầu nào

Model trong game Tic-Tac-Toe mà chúng ta sẽ xây dựng sẽ bao gồm 3 đối tượng chính là:

  • Player
  • Cell
  • Game

Player

Player sẽ chứa 2 thuộc tính cơ bản là Name(lưu tên người chơi) và Value(lưu giá trị chơi của người đó, ở đây là X or O)

public class Player {

    public String name;
    public PlayerValue value;

    public Player(String name, String value) {
        this.name = name;
        if (value.equalsIgnoreCase("x")) {
            this.value = PlayerValue.VALUE_X;
        } else if (value.equalsIgnoreCase("o")) {
            this.value = PlayerValue.VALUE_O;
        } else {
            this.value = PlayerValue.VALUE_EMPTY;
        }
    }

    public Player(String name, PlayerValue value) {
        this.name = name;
        this.value = value;
    }

    public Player(String name, int value) {
        this.name = name;
        if (value == 0) {
            this.value = PlayerValue.VALUE_X;
        } else if (value == 1) {
            this.value = PlayerValue.VALUE_O;
        } else {
            this.value = PlayerValue.VALUE_EMPTY;
        }
    }

    public enum PlayerValue {
        VALUE_EMPTY("VALUE_EMPTY"),
        VALUE_X("VALUE_X"),
        VALUE_O("VALUE_O");

        private String value = "";

        PlayerValue(String value) {
            this.value = value;
        }

        @Override
        public String toString() {
            String s = super.toString();
            if (s.equals("VALUE_X")) {
                return "X";
            }
            return "O";
        }

    }
}

Cell

Mỗi Cell trong game chỉ chứa 1 đối tượng duy nhất chính là cell của Player nào. Nên ở đây chúng ta chỉ cần:

public class Cell {

    public Player player;

    public Cell(Player player) {
        this.player = player;
    }

    public Cell(String name, String value) {
        this.player = new Player(name, value);
    }

    public boolean isEmpty() {
        return player == null || player.value == Player.PlayerValue.VALUE_EMPTY;
    }
}

Game

Lớp này sẽ đại diện cho trò chơi Tic-Tac-Toe ngoài thực tế, tức sẽ có 2 người chơi trong 1 board 3x3 (9 cell). Trong mỗi lượt người chơi sẽ đánh vào ô của mình (phải là ô empty), ở đây mình gọi là currentPlayer và cuối cùng là người chiến thắng.

Người chiến thắng winner mình sẽ sử dụng MultableLiveData để lưu lại, thông báo cho view biết ai sẽ người chiến thắng. Nếu bạn chưa biết về MultableLiveData thì đừng cảm thấy khó khăn, nó khá đơn giản và thông qua đây bạn có thể hiểu cơ bản cách nó làm việc thôi. ^.^

public class Game {

    private static final String TAG = Game.class.getSimpleName();

    private static final int BOARD_SIZE = 3;

    public Player player1;
    public Player player2;

    public Player currentPlayer;

    public Cell[][] cells;

    public MutableLiveData<Player> winners = new MutableLiveData<>();

    public Game(String playerOneName, String playerTwoName) {
        cells = new Cell[BOARD_SIZE][BOARD_SIZE];
        player1 = new Player(playerOneName, "x");
        player2 = new Player(playerTwoName, "o");
        currentPlayer = player1;
    }

    public void switchPlayer() {
        currentPlayer = currentPlayer == player1 ? player2 : player1;
    }
}

Ở đối tượng Game này chúng ta cần biết khi nào game kết thúc, đó là lý do chúng ta cần kiểm tra dòng và cột trên board sau khi 1 player đã đánh vào ô trống trên Board. Game sẽ là kết thúc khi có 3 cell giống hệt nhau theo chiều dọc, chiều ngang hoặc theo đường chéo, hoặc là board của game đã full không còn di chuyển được nữa (Lúc này ko ai là người chiến thắng cả)

public boolean isGameEnded() {
    if (hasThreeSameOnHorizontalCells() || hasThreeSameOnVerticalCells()
            || hasThreeSameOnDiagonalCells()) {
        winners.setValue(currentPlayer);
        return true;
    }

    if (isBoardFull()) {
        winners.setValue(null);
        return true;
    }

    return false;
}

public boolean hasThreeSameOnHorizontalCells() {
    Player.PlayerValue value = currentPlayer.value;

    return areEquals(value, cells[0][0], cells[1][0], cells[2][0])
            || areEquals(value, cells[0][1], cells[1][1], cells[2][1])
            || areEquals(value, cells[0][2], cells[1][2], cells[2][2]);
}

public boolean hasThreeSameOnVerticalCells() {
    Player.PlayerValue value = currentPlayer.value;

    return areEquals(value, cells[0][0], cells[0][1], cells[0][2])
            || areEquals(value, cells[1][0], cells[1][1], cells[1][2])
            || areEquals(value, cells[2][0], cells[2][1], cells[2][2]);
}

public boolean hasThreeSameOnDiagonalCells() {
    Player.PlayerValue value = currentPlayer.value;

    return areEquals(value, cells[0][0], cells[1][1], cells[2][2])
            || areEquals(value, cells[0][2], cells[1][1], cells[2][0]);
}

public boolean isBoardFull() {
    for (int i = 0; i < BOARD_SIZE; i++) {
        for (int j = 0; j < BOARD_SIZE; j++) {
            if (cells[i][j] == null || cells[i][j].isEmpty()) {
                return false;
            }
        }
    }
    return true;
}

public boolean areEquals(Player.PlayerValue value, Cell... cells) {
    for (Cell cell : cells) {
        if (cell == null || cell.isEmpty() || cell.player != currentPlayer || cell.player.value != value) {
            return false;
        }
    }
    return true;
}

Cuối cùng thì sau khi game kết thúc, ta cần bắt đầu lại một game mới để người chơi có thể tiếp tục chơi phải không nào:

public void reset() {
    currentPlayer = player1;
    cells = new Cell[BOARD_SIZE][BOARD_SIZE];
}

Tổng kết

Như các bạn có thể thấy, Model trong MVVM sẽ không chỉ là đại diện cho dữ liệu, mà còn chứa trạng thái và các xử lý logic của đối tượng đó nữa. Nó mô tả các yếu tố nền tảng trong ứng dụng của chúng ta, kiểm tra khi nào game còn có thể tiếp tục hay đã có thể kết thúc hay chưa và biết cần lầm gì khi game kết thúc luôn. Tất cả logic đã được viết tại Model, bây giờ chúng ta cần phải có một thành phần để dàn dựng lên trò chơi của chúng ta. Nó chính là ViewModel, chúng ta sẽ cùng xây dựng nó trong phần 3 của loạt bài viết này nha.

Điểm hay ho: Sự thật trong trong mô hình MVVM thì Model là lớp có thể test rất dễ dàng. Nó không cần sự tương tác từ UI, điều đó có nghĩa là chỉ cần Junit test là chúng ta có thể test tính đúng đắn của tất cả các xử lý logic ở đây rồi.

Cảm ơn các bạn đã theo dõi nha, hẹn gặp lại các bạn trong phần sau của bài viết, chúng ta sẽ cùng tìm hiểu về ViewModel trong MVVM.

Được tạo bởi Blogger.