You are on page 1of 32

Chương 3.

THIẾT KẾ GIAO DIỆN

3.1. Giao diện đăng nhập:

3.1.1. View
View

GroupView Control

Layout Widget (compound control)

Control Control Control

Trong Android, giao diện người dùng được tạo bằng cách sử dụng các thành phần
gọi là View. Hệ thống Android có một cấu trúc phân cấp để sắp xếp các màn hình. Mỗi
màn hình (screen) bao gồm các bố cục (Layout) và các phần tử giao diện (widget) được
sắp xếp theo một trình tự cụ thể. Để hiển thị một màn hình, trong phương thức
onCreate của mỗi Activity, chúng ta cần gọi phương thức
setContentView(R.layout.main). Phương thức này sẽ tải giao diện từ tệp main.xml và
biên dịch nó thành mã bytecode để hiển thị trên màn hình thiết bị Android.

3.1.2. VIEWGROUP:

ViewGroup là sự mở rộng của class View hay nói cách khác ViewGroup chính là
các WidgetLayout được dùng để bố trí các đối tượng khác trong một screen.Có các loại
ViewGroup như sau:
- Linear Layout

LinearLayout được dùng để bố trí các thành phần giao diện theo chiều ngang hoặc
chiều dọc nhưng trên một line duy nhất mà không có xuống dòng.
fragment_home.xml

- Frame layout
FrameLayout được dùng để bố trí các đối tượng theo kiểu giống như làcác

Layer trong Photoshop. Những đối tượng nào thuộc Layer bên dưới thì sẽ bị che khuất
bởi các đối tượng thuộc Layer nằm trên. FrameLayer thường được sử dụng khi muốn tạo
ra các đối tượng có khung hình bên ngoài chẳng hạn như contact image button.

3.1.3. Các Button:


Lý do mà nút điều khiển (button) được giới thiệu đầu tiên trong số các phần tử
giao diện khác là bởi vì đây là một trong những phần tử được sử dụng phổ biến nhất
trong hầu hết các ứng dụng Android.
Có hai cách để thiết kế giao diện với một nút điều khiển như sau:
<com.google.android.gms.common.SignInButton
android:id="@+id/sign_in_button"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btn_guest"
android:backgroundTint="@color/black_200"
android:text="@string/guest"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>

Đây là cách làm ra một button đơn giản. Đoạn mã trên sau khi chạy:

Để khai báo một Button trong code ta làm như sau:


signInGuest.setOnClickListener(view -> {
mAuth.signInAnonymously().addOnCompleteListener(this, task -> {
if (task.isSuccessful()) {
// Sign in success, update UI with the signed-in user's
information
Log.d("TAG", "signInAnonymously:success");
FirebaseUser user = mAuth.getCurrentUser();
updateUI(user);
} else {
// If sign in fails, display a message to the user.
Log.w("TAG", "signInAnonymously:failure", task.getException());
Toast.makeText(SignInMethodActivity.this, "Authentication
failed.",
Toast.LENGTH_SHORT).show();
updateUI(null);
}

});
});

3.2. Giao diện trang chủ:

3.2.1. Navigation

Fragment là một phần tử giao diện hoặc hành vi có tính module và có thể tái sử
dụng, có thể kết hợp với các Fragment khác trong một Activity để tạo giao diện đa bảng.
Fragment được sử dụng để đại diện cho một phần của giao diện người dùng và thường
được sử dụng để xây dựng bố cục linh hoạt và đáp ứng cho cả điện thoại và máy tính
bảng.

Sử dụng câu lệnh <fracment /> để gọi các đối tượng như sau:
activity_main.xml

Ví dụ: Thử tạo các phần tử có hành vi liên quan đến các fragment khác như: Home,
Saved, Notification

Tạo class HomeFragment.java để liên kết các UI lại với nhau:


o Nó khởi tạo giao diện cho màn hình chính (home screen), bao gồm một carousel,
một danh sách các mục tin tức và một danh sách các danh mục tin tức.
o Nó thiết lập giao diện của carousel bằng cách sử dụng RecyclerView với
CarouselLayoutManager để hiển thị một danh sách quay vòng của các hình ảnh.
o Nó tạo một adapter cho carousel và điền dữ liệu bằng các URL hình ảnh.
o Nó thiết lập một danh sách các danh mục tin tức bằng cách sử dụng một
RecyclerView khác.
o Nó khởi tạo một NewsListAdapter và sử dụng nó để hiển thị danh sách các mục
tin tức trong một ListView. Danh sách này được điền dữ liệu từ một cơ sở dữ liệu
Firebase.
o Nó xử lý sự kiện nhấp vào các mục trong danh sách tin tức, mở một
DetailActivity để hiển thị chi tiết về mục tin tức được chọn.
public class HomeFragment extends Fragment {

private FragmentHomeBinding binding;


ArrayList<NewsItem> listNewsItem;
ArrayList<Category> categoryList;
ArrayList<String> carouselList;
NewsListAdapter adapter;
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
HomeViewModel homeViewModel =
new ViewModelProvider(this).get(HomeViewModel.class);

binding = FragmentHomeBinding.inflate(inflater, container, false);


View root = binding.getRoot();

carouselList = new ArrayList<>();


listNewsItem = new ArrayList<>();
categoryList = new ArrayList<>();

final RecyclerView carouselRecyclerView =binding.carouselRecyclerView;


final ListView listView = root.findViewById(R.id.news);
final RecyclerView recyclerView =
root.findViewById(R.id.category_list);

adapter = new NewsListAdapter(getContext(),listNewsItem);


Firebase firebase = new Firebase(listView,listNewsItem,
root.getContext(),adapter);

CarouselLayoutManager carouselLayoutManager = new


CarouselLayoutManager();
carouselRecyclerView.setLayoutManager(carouselLayoutManager);

carouselList.add("https://encrypted-tbn0.gstatic.com/images?
q=tbn:ANd9GcRL3s2YxzGwlKiBhyfg_N6HCS2BeurPm9ySzpXisRjvE0N8kZGcb4UvJSTlSTJvjCFy
fZ0&usqp=CAU");
carouselList.add("https://media.istockphoto.com/id/505266386/photo/
vibrant-sunrise-at-buttermere-with-reflections-and-snow-on-mountains.jpg?
s=612x612&w=0&k=20&c=MDDSSjCvRrGHRSQ_x-lXcR2_stsGT65Mz6Th5-BSfow=");
carouselList.add("https://media.istockphoto.com/id/516415148/photo/
dawn-at-buttermere-with-dramatic-clouds-and-lone-tree.jpg?
s=612x612&w=0&k=20&c=Tq1ztNX0LJSrGWo--iykXKHoU6pSQALBBXz1ZvwGYQk=");
carouselList.add("https://media.istockphoto.com/id/516415148/photo/
dawn-at-buttermere-with-dramatic-clouds-and-lone-tree.jpg?
s=612x612&w=0&k=20&c=Tq1ztNX0LJSrGWo--iykXKHoU6pSQALBBXz1ZvwGYQk=");
CarouselAdapter carouselAdapter = new
CarouselAdapter(carouselList,root.getContext());
carouselRecyclerView.setAdapter(carouselAdapter);

LinearLayoutManager layoutManager = new


LinearLayoutManager(getContext(),LinearLayoutManager.HORIZONTAL,false);
recyclerView.setLayoutManager(layoutManager);

firebase.getCategory(recyclerView,categoryList);
firebase.getNews();

listView.setOnItemClickListener((adapterView, view, i, l) -> {


Intent intent = new Intent(getContext(),DetailActivity.class);
NewsItem item = listNewsItem.get(i);
item.setView_count(item.getView_count()+1);
listNewsItem.clear();
intent.putExtra("ITEM", item);
startActivity(intent);
});

final TextView textView = binding.textHome;


listView.setEmptyView(textView);
homeViewModel.getText().observe(getViewLifecycleOwner(),
textView::setText);
//
homeViewModel.getList().observe(getViewLifecycleOwner(),listNewsItem::addAll);
return root;
}

@Override
public void onResume() {
super.onResume();
adapter.notifyDataSetChanged();
}

@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}

Tạo class HomeViewModel.java:


 Sử dụng kiến trúc MVVM để phân tách dữ liệu liên quan đến giao diện người
dùng khỏi các thành phần giao diện người dùng. Thành phần LiveData đảm bảo
rằng giao diện người dùng có thể phản ứng với các thay đổi trong dữ liệu này theo
cách có ý thức về vòng đời, điều quan trọng để duy trì ứng dụng Android linh hoạt
và hoạt động tốt.

public class HomeViewModel extends ViewModel {

private final MutableLiveData<String> mText;


private final LiveData<ArrayList<NewsItem>> listNewsItemLiveData;

public HomeViewModel() {
listNewsItemLiveData = new MutableLiveData<>();
mText = new MutableLiveData<>();
mText.setValue("Nothing Here!!");
}

public LiveData<String> getText() {


return mText;
}
public LiveData<ArrayList<NewsItem>> getList() {
return listNewsItemLiveData;
}
}

Tạo navigation graph như sau:


o Tìm kiếm “NavHostFragment”, nhấn giữ và kéo và activiti_main.xml, đặt tên là
nav_host_fragment_activity_main

o Tại folder res -> navigation, tại file mobile_navigation tạo 3 UI destination tương
ứng với 3 file fragment XML “home, save, notification” như sau:
o Tại file MainActivity.java ta viết code chính để điều hướng các fragment “home,
save, notification”:
o public class MainActivity extends AppCompatActivity {

private ActivityMainBinding binding;

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

binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());

BottomNavigationView navView = findViewById(R.id.nav_view);


// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
AppBarConfiguration appBarConfiguration = new
AppBarConfiguration.Builder(
R.id.navigation_home, R.id.navigation_saved,
R.id.navigation_notifications)
.build();
NavController navController = Navigation.findNavController(this,
R.id.nav_host_fragment_activity_main);
NavigationUI.setupActionBarWithNavController(this,
navController, appBarConfiguration);
NavigationUI.setupWithNavController(binding.navView,
navController);
}
}

Tại folder res -> menu tạo file bottom_nav_menu.xml tương ứng với class
BottomNavigationView trong MainActivity.java dùng để điều hướng giao diện: HOME,
SAVED, NOTIFICATION:

Giao diện chính của app

3.2.2. Home:
fragment_home.xml

3.2.2.1 Carosel

- Để hiển thị danh sách dữ liệu trong carosel thì sử dụng RecyclerView và
Adapter. Nó được sử dụng để cập nhật nội dung của các mục danh sách và
thực hiện các tác vụ như gán dữ liệu vào các phần tử giao diện ImageView
trong mỗi mục của danh sách. Sử dụng thêm thư việc Picasso để lấy dữ liệu
hình ảnh từ trình duyệt.

 Bước 1: Tạo class CaroselAdapter như sau:

public class CarouselAdapter extends


RecyclerView.Adapter<CarouselAdapter.ViewHolder> {
private final ArrayList<String> carousel_list;
final Context context;
private OnClickListener onClickListener;
public CarouselAdapter(ArrayList<String> carouselList, Context context) {
this.carousel_list = carouselList;
this.context = context;
}
}
 Bước 2: Tạo một carosel_item.xml để hiển thị hình ảnh

<com.google.android.material.carousel.MaskableFrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/carousel_item_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:circularflow_defaultRadius="12dp"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:foreground="?attr/selectableItemBackground"
app:shapeAppearanceOverlay="@style/roundedCorners"
app:shapeAppearance="?attr/shapeAppearanceCornerExtraLarge">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/carousel_image_view"
android:layout_width="match_parent"
android:adjustViewBounds="true"
android:layout_height="match_parent"
android:contentDescription="@string/title_logo"
android:scaleType="centerCrop"/>
</com.google.android.material.carousel.MaskableFrameLayout>

File carosel_item.xml sẽ liên kết với id carousel_recycler_view trong file


fragment_home.xml

- Bước 3: Với những giá trị đã tạo ta tiếp tục viết code cho trong file
CaroselAdapter.java.java:

@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int
viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.caroucel_item, parent, false);
return new ViewHolder(view);
}

@Override
public void onBindViewHolder(@NonNull ViewHolder holder,
@SuppressLint("RecyclerView") int position) {

Picasso.with(context).load(carousel_list.get(position)).into(holder.getImageVi
ew());
holder.getImageView().setOnClickListener(view -> {
// if (onClickListener != null) {
// onClickListener.onClick(position,
carousel_list.get(position));
// }
});
}

public void setOnClickListener(OnClickListener onClickListener) {


this.onClickListener = onClickListener;
}

public interface OnClickListener {


void onClick(int position, Category model);
}
@Override
public int getItemCount() {
return carousel_list.size();
}

public static class ViewHolder extends RecyclerView.ViewHolder {


private final ImageView imageView;

public ViewHolder(View view) {


super(view);
imageView = view.findViewById(R.id.carousel_image_view);

}
public ImageView getImageView() {
return imageView;
}

3.2.2.2. Category:

 Bước 1: Tạo class Category để set() và get() name, id cho mục danh
sách như sau:
Tạo thêm class CategoryAdapter để liên kết dữ liệu giữa các mục trong danh sách tương
tự như Carosel. Nó được sử dụng để cập nhật nội dung của các mục danh sách và thực
hiện các tác vụ như gán dữ liệu vào các phần tử giao diện TextView trong mỗi mục của
danh sách.
public class CategoryAdapter extends
RecyclerView.Adapter<CategoryAdapter.ViewHolder> {
}

 Bước 2: Tạo một catogory_item.xml để xem hình ảnh

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<com.google.android.material.chip.Chip
android:layout_marginEnd="6dp"
android:id="@+id/chip_item"
android:text="@string/title_logo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</FrameLayout>

File catogory_item.xml sẽ liên kết với id category_list trong fragment_home.xml

- Bước 3: Với những giá trị đã tạo ta tiếp tục viết code cho trong file
CategoryAdapter.java.java:
private final ArrayList<Category> category_list;

private OnClickListener onClickListener;


public CategoryAdapter(ArrayList<Category> categoryList) {
category_list = categoryList;
}

@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType)
{
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.catogory_item, parent, false);
return new ViewHolder(view);
}

@Override
public void onBindViewHolder(@NonNull ViewHolder holder,
@SuppressLint("RecyclerView") int position) {
holder.getTextView().setText(category_list.get(position).getName());
holder.getTextView().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (onClickListener != null) {
onClickListener.onClick(position,
category_list.get(position));
}
}
});
}

public void setOnClickListener(OnClickListener onClickListener) {


this.onClickListener = onClickListener;
}

public interface OnClickListener {


void onClick(int position, Category model);
}
@Override
public int getItemCount() {
return category_list.size();
}

public static class ViewHolder extends RecyclerView.ViewHolder {


private final TextView textView;

public ViewHolder(View view) {


super(view);
textView = view.findViewById(R.id.chip_item);

}
public TextView getTextView() {
return textView;
}
}

3.2.2.3. News

 Bước 1: Tạo class NewsItem để set() và get() các dữ liệu cần thiết cho mục
danh sách news như sau:
public class NewsItem implements Serializable {
}

Tạo thêm class NewsListAdapter để hiện dữ liệu liệu trong phần ListView:
public class NewsListAdapter extends BaseAdapter {
}

 Bước 2: Tạo một news_item.xml để hiển thị bài post:

File news_item.xml sẽ liên kết với id news trong fragment_home.xml

- Bước 3: Với những giá trị đã tạo ta tiếp tục viết code cho trong files
NewsItem.java và NewsListAdapter.java:
 NewsItem:

Tạo constructor khai báo properties trong class:


public NewsItem(String news_image, String news_author_avatar, String
news_title, String newsAuthorName, String news_created_at, Boolean isSaved,
String detail, int category_id, int likeCount, int viewCount) {
this.news_image = news_image;
this.news_author_avatar = news_author_avatar;
this.news_title = news_title;
this.news_author_name = newsAuthorName;
this.news_created_at = news_created_at;
this.is_saved = isSaved;
this.detail = detail;
this.category_id = category_id;
this.like_count = likeCount;
this.view_count = viewCount;
}

Tạo các methods getter và setter:

 public void setCategory_id(int category_id) {


this.category_id = category_id;
}

public void setLike_count(int like_count) {


this.like_count = like_count;
}

public void setView_count(int view_count) {


this.view_count = view_count;
}

public String getNew_id() {


return new_id;
}

public void setNew_id(String new_id) {


this.new_id = new_id;
}

public int getLike_count() {


return like_count;
}

public int getView_count() {


return view_count;
}

public int getCategory_id() {


return category_id;
}

public Boolean getIs_saved() {


return is_saved;
}

public void setIs_saved(Boolean is_saved) {


this.is_saved = is_saved;
}

public String getDetail() {


return detail;
}

public void setDetail(String detail) {


this.detail = detail;
}

public String getNews_author_name() {


return news_author_name;
}

public void setNews_author_name(String news_author_name) {


this.news_author_name = news_author_name;
}
public String getNews_image() {
return news_image;
}

public void setNews_image(String news_image) {


this.news_image = news_image;
}

public String getNews_author_avatar() {


return news_author_avatar;
}

public void setNews_author_avatar(String news_author_avatar) {


this.news_author_avatar = news_author_avatar;
}

public String getNews_title() {


return news_title;
}

public void setNews_title(String news_title) {


this.news_title = news_title;
}

public String getNews_created_at() {


return news_created_at;
}

public void setNews_created_at(String news_created_at) {


this.news_created_at = news_created_at;
}
 NewsListAdapter:

Tạo constructor, get() và set() cho Count, Id:

final Context context;


final ArrayList<NewsItem> listNewsItem;
public NewsListAdapter(Context context, ArrayList<NewsItem> listNewsItem) {
this.context = context;
this.listNewsItem = listNewsItem;
}

@Override
public int getCount() {
return this.listNewsItem.size();
}

@Override
public Object getItem(int i) {
return this.listNewsItem.get(i);
}

@Override
public long getItemId(int i) {
return 0;
}

o Tạo method getView để lấy các thuộc tính từ news_item.xml:


 Sử dụng thư viện Picasso để lấy image từ file Json.
 Tạo sự kiện setOnClickListener cho method setActiveButton
sau đó update items lên Firebase.
 Khởi tạo method setActiveButton để set image và các thuộc tính
UI, bảng màu

public View getView(int i, View view, ViewGroup viewGroup) {


LayoutInflater layoutInflater = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View viewProduct;
if (view == null) {
viewProduct = layoutInflater.inflate( R.layout.news_item,null);
}else{
viewProduct= view;
}

NewsItem item = (NewsItem) getItem(i);


ImageButton savedBtn = viewProduct.findViewById(R.id.btn_book_mark);
ImageView newsImage = (viewProduct.findViewById(R.id.news_image));
ImageView newsAuthorAvatar =
(viewProduct.findViewById(R.id.news_author_avatar));
TextView newsTitle = (viewProduct.findViewById(R.id.news_title));
TextView newsAuthorName =
(viewProduct.findViewById(R.id.news_author_name));
TextView newsCreatedAt = (viewProduct.findViewById(R.id.news_created_at));

Picasso.with(context).load(item.getNews_image()).into(newsImage);
Picasso.with(context).load(item.getNews_author_avatar()).into(newsAuthorAvatar
);

if (item.getIs_saved()){
setActiveButton(savedBtn,item.getIs_saved());
}

savedBtn.setOnClickListener(view1 -> {;
boolean isAdd = !item.getIs_saved();

new Firebase().savedItem(item.getNew_id(),item.getIs_saved());
item.setIs_saved(isAdd);
setActiveButton(savedBtn, isAdd);
});
newsTitle.setText(item.getNews_title());
newsAuthorName.setText(item.getNews_author_name());
newsCreatedAt.setText(item.getNews_created_at());

return viewProduct;
}

Tại id: nav_view tức là thanh điều hướng đến các fragment khác bằng việc sử dụng

3.3. Giao diện lưu bài viết

 Bước 1: Tạo class SavedFragment và SaveViewModel như sau:


SavedFragment:
public class SavedFragment extends Fragment {
}
SaveViewModel:
public class SavedViewModel extends ViewModel {
}

 Bước 2: Tạo file fragment_saved.xml như sau:

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:paddingVertical="12dp"
android:paddingHorizontal="6dp"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/user_id"
android:layout_marginEnd="12dp"
android:text="user id"
android:layout_width="0dp"
android:layout_weight="0.9"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/btn_sign_Out"
android:text="@string/sign_out"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>

<ListView
android:id="@+id/saved_news"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="#00000000"
android:dividerHeight="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:layout_editor_absoluteY="238dp" />

<TextView
android:id="@+id/text_dashboard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:textAlignment="center"
android:textSize="20sp"
android:textAppearance="?android:textAppearanceMedium"
android:visibility="gone"/>
</LinearLayout>
 Bước 3: Viết tiếp code cho method SavedFragment như sau:
SavedFragment:

o Sử dụng Data Binding trong FragmentSavedBinding, cho phép truy cập các phần
tử giao diện trong tệp layout fragment_saved.xml của SavedFragment bằng cách
sử dụng biến binding, giúp tương tác với các phần tử giao diện người dùng một
cách dễ dàng và linh hoạt hơn thông qua Data Binding.

o Sử dụng Firebase để get data đã Save từ NewsList.

o Sử dụng method onResumeđể thực hiện các nhiệm vụ như bắt đầu animation, làm
mới dữ liệu hoặc tải các tài nguyên cần thiết cho giao diện người dùng của
fragment khi nó trở nên hiển thị.
o Sử dụng method onDestroyView giúp giải phóng bộ nhớ và ngăn chặn rò rỉ bộ
nhớ. Khi view bị hủy bỏ, đó là thời điểm tốt để giải phóng tham chiếu đến các
view và tài nguyên liên quan đến fragment.
o Sử dụng FirebaseAuth để SignOut khỏi UserId người dùng hiện tại. Khi ấn Button
Sign Out sẽ đăng xuất trở về màn hình Sign In và xoá dữ liệu người dùng nếu
trước đó sử dụng tài khoản khách.

private FragmentSavedBinding binding;


ArrayList<NewsItem> listNewsItem;

public View onCreateView(@NonNull LayoutInflater inflater,


ViewGroup container, Bundle savedInstanceState) {
SavedViewModel savedViewModel =
new ViewModelProvider(this).get(SavedViewModel.class);

binding = FragmentSavedBinding.inflate(inflater, container, false);


View root = binding.getRoot();

final TextView textView = binding.textDashboard;

FirebaseAuth auth = FirebaseAuth.getInstance();


ListView listView = root.findViewById(R.id.saved_news);
Button signOut = binding.btnSignOut;
TextView userId = binding.userId;
listView.setEmptyView(textView);

listNewsItem = new ArrayList<>();

NewsListAdapter adapter = new


NewsListAdapter(getContext(),listNewsItem);
Firebase firebase = new Firebase(listView,listNewsItem,
root.getContext(),adapter);

firebase.getSaved();

listView.setOnItemClickListener((adapterView, view, i, l) -> {


Intent intent = new Intent(getContext(), DetailActivity.class);
NewsItem item = listNewsItem.get(i);
item.setView_count(item.getView_count()+1);
intent.putExtra("ITEM", item);
startActivity(intent);
});

signOut.setOnClickListener(view -> {
auth.signOut();
});
userId.setText(auth.getUid());
savedViewModel.getText().observe(getViewLifecycleOwner(),
textView::setText);
//
savedViewModel.getList().observe(getViewLifecycleOwner(),listNewsItem::addAll)
;
return root;
}

@Override
public void onResume() {
super.onResume();

@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}

SavedViewModel:
Sử dụng kiến trúc MVVM( Model-View-ViewModel )
LiveData<ArrayList<NewsItem>> listNewsItemLiveData: Đây là một biến
kiểu LiveData chứa một danh sách (ArrayList) các mục tin tức (NewsItem). LiveData
này cho phép theo dõi các thay đổi trong danh sách mục tin tức.
SavedViewModel(): Đây là constructor của lớp SavedViewModel. Trong
constructor này, biến listNewsItemLiveData được khởi tạo và biến mText được khởi tạo
với giá trị ban đầu là "Nothing Here!!!".
LiveData<String> getText(): Phương thức này trả về biến mText dưới dạng
LiveData, cho phép các thành phần giao diện người dùng quan sát giá trị của nó. Phương
thức này được sử dụng để lấy giá trị chuỗi từ biến mText để hiển thị trong giao diện
người dùng.
LiveData<ArrayList<NewsItem>> getList(): Phương thức này trả về biến
listNewsItemLiveData dưới dạng LiveData, cho phép quan sát các thay đổi trong danh
sách mục tin tức. Phương thức này có thể được sử dụng để lấy danh sách mục tin tức và
hiển thị chúng trong giao diện người dùng.

private final MutableLiveData<String> mText;


private final LiveData<ArrayList<NewsItem>> listNewsItemLiveData;

public SavedViewModel() {
listNewsItemLiveData = new MutableLiveData<>();
mText = new MutableLiveData<>();
mText.setValue("Nothing Here!!!");
}

public LiveData<String> getText() {


return mText;
}
public LiveData<ArrayList<NewsItem>> getList() {
return listNewsItemLiveData;
}

3.4. Giao diện thông báo

 Bước 1: Tạo class NotificationsFragment để định nghĩa và quản lý giao


diện người dùng (UI) để hiển thị thông báo trong ứng dụng như sau:
public class NotificationsFragment extends Fragment {
}

 Bước 2: Tạo file fragment_notifications để hiển thị ra màn hình như sau:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="6dp"
tools:context=".ui.notifications.NotificationsFragment">

<ListView
android:divider="#00000000"
android:dividerHeight="12dp"
android:id="@+id/notification_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/text_notifications"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:textAlignment="center"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

Tiếp tục tạo file notification_items.xml để hiển thị thông tin chi tiết các thông báo:

<?xml version="1.0" encoding="utf-8"?>


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
android:layout_width="match_parent"
android:padding="12dp"
android:gravity="center_vertical"
android:layout_height="match_parent">

<ImageView
android:background="@drawable/rounded_shape"
android:id="@+id/notification_icon"
android:src="@drawable/baseline_notifications_active_24"
android:contentDescription="@string/title_logo"
android:layout_width="wrap_content"
android:layout_marginEnd="12dp"
android:layout_height="wrap_content"
app:tint="@color/primary" />
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/notification_title"
android:text="title"
android:textSize="18sp"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/notification_content"
android:textSize="12sp"
android:textColor="@color/gray_200"
android:text="content"
android:layout_width="wrap_content"
android:layout_height="16dp"/>
</LinearLayout>
</LinearLayout>

 Bước 3: Viết tiếp code cho class NotificationsFragment.java.

Khởi tạo một NotificationsViewModel để quản lý và theo dõi sự thay đổi


trong dữ liệu hoặc hành vi giao diện của fragment. ViewModel là một phần
của Android Architecture Components và được sử dụng để tách xử lý dữ liệu
và logic giao diện người dùng.
Truy xuất dữ liệu thông báo, có thể là từ máy chủ Firebase, và điền dữ liệu
này vào các phần tử giao diện. Trong trường hợp này, nó sử dụng một
ListView để hiển thị danh sách thông báo và đặt một "giao diện trống" khi
không có thông báo (là TextView với ID là textNotifications).
Sử dụng lớp FragmentNotificationsBinding để truy cập và điều khiển các
view và phần tử giao diện trong bố cục của fragment. Lớp này được tạo ra bởi
thư viện Android Data Binding và được sử dụng để đơn giản hóa các nhiệm vụ
liên quan đến giao diện người dung.
Sử dụng firebase để lấy tài nguyên về phần ListView.
private FragmentNotificationsBinding binding;

public View onCreateView(@NonNull LayoutInflater inflater,


ViewGroup container, Bundle savedInstanceState) {
NotificationsViewModel notificationsViewModel =
new ViewModelProvider(this).get(NotificationsViewModel.class);
binding = FragmentNotificationsBinding.inflate(inflater, container,
false);
View root = binding.getRoot();

ArrayList<NotificationItems> notificationItems = new ArrayList<>();

final ListView listView = binding.notificationList;


final TextView textView = binding.textNotifications;

new Firebase().getNotifications(getContext(),listView,notificationItems);

listView.setEmptyView(binding.textNotifications);

notificationsViewModel.getText().observe(getViewLifecycleOwner(),
textView::setText);
return root;
}

@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}

Giống như phần Save ta sử dụng Kiến trúc: MVVM( Model-View-


ViewModel )

Tạo class NotificationsViewModel mở rộng từ ViewModel: Đó là một lớp con


của lớp ViewModel của Android, được sử dụng để lưu trữ và 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.
MutableLiveData<String> mText: Đây là một biến kiểu MutableLiveData chứa
một chuỗi (String). MutableLiveData cho phép thay đổi giá trị của nó, và nó được sử
dụng để lưu trữ thông báo.
NotificationsViewModel(): Đây là constructor của lớp NotificationsViewModel.
Trong constructor này, biến mText được khởi tạo và giá trị ban đầu của nó được đặt
thành "Không có gì ở đây!!!".
LiveData<String> getText(): Đây là một phương thức trả về biến mText dưới
dạng LiveData, cho phép các thành phần giao diện người dùng quan sát (observe) giá trị
của nó. Phương thức này được sử dụng để lấy giá trị chuỗi từ biến mText để hiển thị
trong giao diện người dùng.

private final MutableLiveData<String> mText;

public NotificationsViewModel() {
mText = new MutableLiveData<>();
mText.setValue("Nothing Here!!!");
}
public LiveData<String> getText() {
return mText;
}

 Bước 4: Tạo class NotificationItems.java được sử dụng để đại diện cho các
thông báo, get và set. Class này có các trường dữ liệu (properties) như noti_id,
title, content, và type, được sử dụng để lưu trữ thông tin liên quan đến một
thông báo.
o noti_id: Là một chuỗi (String) đại diện cho ID của thông báo.
o title: Là một chuỗi (String) đại diện cho tiêu đề của thông báo.
o content: Là một chuỗi (String) đại diện cho nội dung của thông báo.
o type: Là một chuỗi (String) đại diện cho loại thông báo.

private String noti_id;


private String title;
private String content;
private String type;

public NotificationItems(String id, String title, String content, String type)


{
this.noti_id = id;
this.title = title;
this.content = content;
this.type = type;
}

public NotificationItems() {
}

public String getNoti_id() {


return noti_id;
}

public void setNoti_id(String noti_id) {


this.noti_id = noti_id;
}

public String getTitle() {


return title;
}

public void setTitle(String title) {


this.title = title;
}

public String getContent() {


return content;
}

public void setContent(String content) {


this.content = content;
}
public String getType() {
return type;
}

public void setType(String type) {


this.type = type;
}

 Bước 4: Tạo class NotificationAdapter.java để hiển thị danh sách thông


báo, class này kế thừa tử BaseAdapter.
final Context context;
final ArrayList<NotificationItems> itemsArrayList;

public NotificationAdapter(Context context, ArrayList<NotificationItems>


itemsArrayList) {
this.context = context;
this.itemsArrayList = itemsArrayList;
}

@Override
public int getCount() {
return itemsArrayList.size();
}

@Override
public Object getItem(int i) {
return itemsArrayList.get(i);
}

@Override
public long getItemId(int i) {
return 0;
}

@Override
public View getView(int i, View view, ViewGroup viewGroup) {

LayoutInflater layoutInflater = (LayoutInflater)


context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View viewProduct;
if (view == null) {
viewProduct =
layoutInflater.inflate( R.layout.notification_items,null);
}else{
viewProduct= view;
}

NotificationItems item = (NotificationItems) getItem(i);


TextView notification_title =
(viewProduct.findViewById(R.id.notification_title));
TextView notification_content =
(viewProduct.findViewById(R.id.notification_content));
Log.d("TAG", "getNotifications: "+item.getTitle());

notification_title.setText(item.getTitle());
notification_content.setText(item.getContent());
return viewProduct;
}

3.5. Giao diện hiển thị thông tin News

 Bước 1: Tạo class DetailActivity để hiển thị thông tin chi tiết về tin tức bao
gồm tiêu đề, tác giả, số lần xem, số lượt thích và mô tả. Người dùng cũng có
thể thích/không thích mục tin tức.
public class DetailActivity extends AppCompatActivity {
}

 Bước 2: Tạo file activity_detail.xml để hiển thị ra màn hình như sau:

 Bước 3: Tiếp tục viết code cho class DetailActivity.java như sau:
 onCreate: Phương thức này được gọi khi hoạt động được tạo. Nó thiết lập giao
diện người dùng và xử lý các hành động liên quan đến chế độ xem chi tiết. Nó bao
gồm các bước sau:
o Đặt tiêu đề thanh hành động và cho phép nút quay lại.
o Khởi tạo và tìm các tham chiếu đến các thành phần giao diện người dùng
(ví dụ: TextView, ImageView, LinearLayout).
o Truy xuất mục tin tức (có thể được truyền qua intent).
o Khởi tạo lớp Firebase để tương tác với Firebase Realtime Database.
o Kiểm tra xem người dùng đã thích mục tin tức này trước đó chưa và thay
đổi màu nút thích tương ứng.
 btn_like.setOnClickListener: Trình nghe này được kích hoạt khi người dùng
nhấn nút thích. Nó chuyển đổi chức năng thích/không thích và cập nhật số lượt
thích trong Firebase Realtime Database.
 firebase.getDetail: Phương thức này truy xuất thông tin chi tiết về mục tin tức từ
Firebase Realtime Database, bao gồm hình ảnh biểu ngữ, thông tin tác giả, tiêu đề,
số lượt thích, số lượt xem, tên tác giả, và mô tả tin tức. Thông tin này sau đó được
sử dụng để điền vào các thành phần giao diện người dùng.
 formatValue: Phương thức này định dạng giá trị số của số lần xem và số lượt
thích thành một định dạng thân thiện với người dùng hơn, ví dụ, chuyển đổi số lớn
thành K (nghìn), M (triệu), B (tỷ), v.v.
 onOptionsItemSelected: Phương thức này được sử dụng để xử lý các tùy chọn
được chọn trong thanh hành động. Nếu người dùng chọn nút quay lại, hoạt động sẽ
kết thúc và người dùng trở lại màn hình trước đó.
 formatValue: Phương thức này được sử dụng để định dạng số lượt xem và số lượt
thích.

TextView textView;
TextView tv_item_title, tv_item_like_count, tv_item_view_count,tv_author_name;
ImageView iv_item_banner,iv_author_avatar;

LinearLayout btn_like;
Firebase firebase;
@SuppressLint("MissingInflatedId")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
ActionBar actionBar = getSupportActionBar();

// showing the back button in action bar


assert actionBar != null;
actionBar.setTitle("Happy reading");
actionBar.setDisplayHomeAsUpEnabled(true);

iv_item_banner = findViewById(R.id.item_banner);
iv_author_avatar = findViewById(R.id.item_author_avatar);
tv_item_title = findViewById(R.id.item_title);
tv_item_view_count = findViewById(R.id.item_view_count);
tv_item_like_count = findViewById(R.id.item_like_count);
tv_author_name = findViewById(R.id.item_author_name);
textView = findViewById(R.id.item_detail);

ImageView imageViewLike = findViewById(R.id.like_icon);


btn_like = findViewById(R.id.btn_like);

NewsItem items = (NewsItem)getIntent().getSerializableExtra("ITEM");


assert items != null;
firebase = new Firebase();

DatabaseReference likesRef = firebase.updateLikeCount();


likesRef
.child(items.getNew_id())
.get()
.addOnCompleteListener(task -> {
if (task.getResult().exists()){

imageViewLike.setColorFilter(ContextCompat.getColor(getApplicationContext(),R.
color.primary), android.graphics.PorterDuff.Mode.SRC_IN);
}
});

firebase.updateViewCount(items.getNew_id(),items.getView_count());

btn_like.setOnClickListener(view -> {

String id = items.getNew_id();
likesRef.child(id)
.get()
.addOnCompleteListener(task -> {
int like = items.getLike_count();
int color = R.color.primary;
if (task.getResult().exists()){
like -=1;
items.setLike_count(like);
likesRef.removeValue();
color = R.color.gray_200;
}
else {
like+=1;
likesRef.child(id).setValue(id);
}
DatabaseReference ref =
firebase.getmDatabase().child(Firebase.NEWS).child(id).child(Firebase.LIKE_CO
UNT);
ref.setValue(like);

imageViewLike.setColorFilter(ContextCompat.getColor(getApplicationContext(),co
lor), android.graphics.PorterDuff.Mode.SRC_IN);
});
});
firebase.getDetail(items.getNew_id()).addValueEventListener(new
ValueEventListener() {
@SuppressLint("SetTextI18n")
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
NewsItem item = snapshot.getValue(NewsItem.class);
assert item != null;

Picasso.with(getApplicationContext()).load(item.getNews_image()).into(iv_item_
banner);

Picasso.with(getApplicationContext()).load(item.getNews_author_avatar()).into(
iv_author_avatar);
tv_item_title.setText(item.getNews_title());
tv_item_like_count.setText(formatValue(item.getLike_count()));
tv_item_view_count.setText(formatValue(item.getView_count()));
tv_author_name.setText(item.getNews_author_name()+" -
"+item.getNews_created_at());
textView.setText(item.getDetail());
}

@Override
public void onCancelled(@NonNull DatabaseError error) {

}
});

@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == android.R.id.home) {
this.finish();
return true;
}
return super.onOptionsItemSelected(item);
}
public String formatValue(float value) {
String[] arr = {"", "K", "M", "B", "T", "P", "E"};
int index = 0;
while ((value / 1000) >= 1) {
value = value / 1000;
index++;
}
DecimalFormat decimalFormat = new DecimalFormat("#.##");
return String.format("%s %s", decimalFormat.format(value), arr[index]);
}

You might also like