Xử lý TỐI ƯU khi xoay màn hình với Architecture Component ViewModel( Có ví dụ thực tế )
Architecture Component ViewModel được Google cho ra mắt vào tháng 11/2017 cho tới nay nó vẫn còn khá mới mẻ trong cộng đồng Android Developer. Mục đích của sự công bố này nhằm giúp cho việc phát triển ứng dụng ngày càng trở lên thuận tiện, bảo trì và kiểm thử được dễ dàng hơn. Tuy nhiên ban đầu khi ...
Architecture Component ViewModel được Google cho ra mắt vào tháng 11/2017 cho tới nay nó vẫn còn khá mới mẻ trong cộng đồng Android Developer. Mục đích của sự công bố này nhằm giúp cho việc phát triển ứng dụng ngày càng trở lên thuận tiện, bảo trì và kiểm thử được dễ dàng hơn. Tuy nhiên ban đầu khi tiếp xúc sẽ có phần khó khăn để hiểu được kiến trúc này, do vậy trong bài viết mình sẽ kết hợp với ví dụ thực tế để mọi người cùng nắm bắt dễ hơn.
Vấn đề thực tế:
- Khi bạn khởi tạo 1 màn hình (Activity hoặc Fragment) > Việc load data & UI đã xong > Xoay màn hình thiết bị > Việc load data & UI bị thực hiện lại 1 lần nữa. Và việc thực hiện xoay màn hình rất dễ xảy ra trong quá trình sử dụng, bởi người dùng muốn quan sát ở những trạng thái thuận tiện hơn.
Kết quả:
- Việc truyền data từ Activity và Fragment rất có thể bị mất dữ liệu và thực hiện gọi API rất nhiều lần
- Memory leaks
Lợi ích của việc sử dụng Architecture Component ViewModel
Như các bạn thấy trong hình : UI Holder lúc này được thay thế bởi ViewModel điều đó có nghĩa rằng : nó sẽ lưu trữ toàn bộ data UI và quản lý vòng đời của những data đó.
- Bạn sẽ không cần phải lo lắng về vòng đời của dữ liệu hiển thị
- Data sẽ luôn được update, dữ liệu lúc trước và sau khi xoay màn hình hoàn toàn giống nhau.
- Bạn không phải truyền lại data (arguments : id, objects,..) sang Activity or Fragment và gọi lại API lần thứ 2
- Data sẽ chờ đợi bạn, nếu bạn thực hiện việc call API sau đó xoay màn hình thì khi kết quả trả về sẽ được chuyển tới trước khi Activity được khởi tạo lại. Chúng đã được lưu ở ViewModel và bạn lấy ra hiển thị trực tiếp
Cách dùng Architecture Component ViewModel
Những lợi ích khi sử dụng thì chúng ta đều đã rõ vậy thì đến bước triển khai sẽ ra sao? Dưới đây mình có nêu ra 1 trường hợp thực tế như sau:
Android App: Có màn hình hiển thị danh sách những developer có trong 1 công ty nhỏ
Thao tác thông thường sau khi call API để lấy được data mà chúng ta xoay màn hình thì việc call API lại tiếp tục được thực hiện, ở đây data mình initiation ở local. Source code như sau:
MainActivity.java
public class MainActivity extends AppCompatActivity { private UserAdapter mAdapter; private ActivityMainBinding mBinding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main); mAdapter = new UserAdapter(); mBinding.mainList.setLayoutManager(new LinearLayoutManager(this)); mBinding.mainList.addItemDecoration( new DividerItemDecoration(this, DividerItemDecoration.VERTICAL)); mBinding.mainList.setAdapter(mAdapter); // Comment it when use Architecture Components ViewModel mAdapter.setUserList(loadUsers()); } /** * You only use this method when * You want to check rotation screen */ private List<User> loadUsers() { List<User> userList = new ArrayList<>(); userList.add(new User("Khahoo Ajish", "Front-End Developer", R.drawable.khahoo)); userList.add(new User("John Covey", "Back-End Developer", R.drawable.john)); userList.add(new User("Loges Vamber", "iOS Developer", R.drawable.loges)); userList.add(new User("Jung Kim Bap", "Android Developer", R.drawable.jung)); userList.add(new User("Seeng Luse", "AI Developer", R.drawable.seeng)); //Log for easily see Log.i("MainActivity", "loadUsers: ---------> size:" + userList.size()); return userList; } }
1. Khai báo dependencies vào trong project
File path: app/build.gradle
// Architecture components implementation 'android.arch.lifecycle:extensions:1.1.1' implementation 'android.arch.lifecycle:runtime:1.1.1' annotationProcessor 'android.arch.lifecycle:compiler:1.1.1'
2. Tạo 1 class UserViewModel được extends từ AndroidViewModel
UserViewModel.java
public class UserViewModel extends AndroidViewModel { private List<User> userList; public UserViewModel(@NonNull Application application) { super(application); if (userList == null) { setupUserList(); } } public List<User> getUserList() { return userList; } private void setupUserList() { userList = new ArrayList<>(); userList.add(new User("Khahoo Ajish", "Front-End Developer", R.drawable.khahoo)); userList.add(new User("John Covey", "Back-End Developer", R.drawable.john)); userList.add(new User("Loges Vamber", "iOS Developer", R.drawable.loges)); userList.add(new User("Jung Kim Bap", "Android Developer", R.drawable.jung)); userList.add(new User("Seeng Luse", "AI Developer", R.drawable.seeng)); Log.i("UserViewModel", "setupUserList: ---------> size: "+userList.size()); } }
Tiếp theo là tạo một Adapter cho việc custom view của thành viên (developer) trong công ty, mình gọi đó là UserAdapter
UserAdapter.java
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserViewHolder> { private List<User> mUserList; public void setUserList(List<User> userList) { if (mUserList == null) { mUserList = new ArrayList<>(); } mUserList.clear(); mUserList.addAll(userList); notifyDataSetChanged(); } @NonNull @Override public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return new UserViewHolder( ItemUserBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); } @Override public void onBindViewHolder(@NonNull UserViewHolder holder, int position) { if (getItemCount() > 0) holder.bindData(mUserList.get(position)); } @Override public int getItemCount() { return mUserList != null ? mUserList.size() : 0; } class UserViewHolder extends RecyclerView.ViewHolder { private ItemUserBinding mBinding; public UserViewHolder(ItemUserBinding binding) { super(binding.getRoot()); mBinding = binding; } public void bindData(User user) { if (mBinding.getUser() == null && user != null) { mBinding.setUser(user); } } } }
Bonus: Với những bạn lo lắng trong việc "kiếm" những avatar ở đâu thì trong source code của mình đã có hết rồi. Mình đã đính kèm ở cuối bài viết này rồi nha