Ví lập trình android quản lý car

Công ty cổ phần thương mại Vạn Tín Việt

Tổng quan

Đây là ví dụ quản lý car bao gồm các chức năng sau

  1. Hiển thị danh sách car
  2. Xem car
  3. Edit car
  4. Dựng Api cho car

Trong quá trình làm này cũng sẽ phát sinh một số lỗi, nên các bạn cũng nên tìm hiểu kỹ chút để có thể tự fix được nhé

Chúc các bạn thành công

Tạo file Object car

Tên FIle: Car.java

Nơi lưu file: java/com/vantinviet/adayroionline/viewobject

Nội dung file:
package com.vantinviet.adayroionline.viewobject;

import androidx.annotation.NonNull;
import androidx.room.Embedded;
import androidx.room.Entity;

import com.google.gson.annotations.SerializedName;

@Entity(primaryKeys = "id")// đối với mỗi đối tượng sẽ phải có khóa chính thường thì id là khóa chính
public class Car {

@NonNull
@SerializedName("_id")
public String id="";

@SerializedName("car_name") // lấy key theo api trả về
public String carName="";
@SerializedName("car_model")
public String carModel="";
@SerializedName("car_color")
public String carColor="";
@Embedded
@SerializedName("default_photo")
public Image defaultPhoto;
@SerializedName("description") // lấy key theo api trả về
public String carDescription="";
@SerializedName("added_date")
public String addedDate="";

//thêm nhiều thuộc tính nữa ở đây, nhớ thêm cả ở phương thức khởi tạo nữa nhé
//Phương thức khởi tạo
public Car() {

}
public Car(@NonNull String id, String carName,String carModel, String carColor,String carDescription, String added_date) {
this.id = id!=null?id:this.id;
this.carName=carName!=null?carName:this.carName;
this.carModel=carModel!=null?carModel:this.carModel;
this.carColor=carColor!=null?carColor:this.carColor;
this.carDescription= carDescription!=null?carDescription:this.carDescription;
this.addedDate=added_date!=null?added_date:this.addedDate;


}

@Override
public String toString() {
return "Car{" +
"id='" + id + '\'' +
", carName='" + carName + '\'' +
", carModel='" + carModel + '\'' +
", carColor='" + carColor + '\'' +
", defaultPhoto=" + defaultPhoto +
", carDescription='" + carDescription + '\'' +
", addedDate='" + addedDate + '\'' +
'}';
}
}

Tạo file CarDao.java

Tên file: CarDao.java

Nơi lưu file: java/com/vantinviet/adayroionline/db

Nội dung file:

package com.vantinviet.adayroionline.db;

import com.vantinviet.adayroionline.viewobject.Car;
import com.vantinviet.adayroionline.viewobject.User;

import java.util.List;

import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Update;

@Dao
public interface CarDao {

@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertAll(List<Car> cars);

@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(Car car);

@Query("SELECT * FROM Car WHERE id = :id")
LiveData<Car> getCarById(String id);
@Update(onConflict = OnConflictStrategy.REPLACE)
void update(Car car);

@Query("SELECT * FROM Car ORDER BY addedDate desc")
LiveData<List<Car>> getAllCars();

@Query("DELETE FROM Car")
void deleteAll();
@Query("DELETE FROM Car WHERE id = :id")
void deleteCarById(String id);

}

Cấu hình API config trỏ tới server local của bạn

Mở file : java/com/vantinviet/adayroionline/Config.java

Bổ xung biết static sau vào  class Config

public static int LIST_CARS_COUNT = 30;

Điều chỉnh api trỏ tới server của bạnanh34

Tạo các hằng số Constants

Mở file : java/com/vantinviet/adayroionline/utils/Constants.java

Bổ xung dòng sau

String CAR_ID = "car_id";
anh33

Khai báo CarDAO trong PSCoreDb

Mở file : java/com/vantinviet/adayroionline/db/PSCoreDb.java

Import

import com.vantinviet.adayroionline.viewobject.Car;

Bổ xung
@Database(entities = {
    Car.class

}
Bổ sung
trong
public abstract class PSCoreDb extends RoomDatabase {
   abstract public CarDao carDao();


}
Tiếp theo bạn cần tăng version lên
tìm đến dòng có hình dưới đây
tăng giá trị lên 1
anh37

Bổ xung CarDao trong AppModule

Mở file: java/com/vantinviet/adayroionline/di/AppModule.java

import CarDao

import com.vantinviet.adayroionline.db.CarDao;

Bổ xung trong class AppModule

Bổ xung dòng sau vào cuối
@Singleton
@Provides
CarDao provideCarDao(PSCoreDb db){return db.carDao();}

Tạo file ApiResponseGetCarList.java

Tên file: ApiResponseGetCarList.java

Nơi lưu file: java/com/vantinviet/adayroionline/ApiResponse/ApiResponseGetCarList.java

Nội dung file:

package com.vantinviet.adayroionline.ApiResponse;

import com.google.gson.annotations.SerializedName;
import com.vantinviet.adayroionline.viewobject.Car;

import java.util.List;

public class ApiResponseGetCarList {


@SerializedName("result")
public String result;

@SerializedName("code")
public String code;

@SerializedName("errorMessage")
public String errorMessage;

@SerializedName("data")
public List<Car> carList;

public ApiResponseGetCarList(String result, String code, String errorMessage, List<Car> carList) {
this.result = result;
this.code = code;
this.errorMessage = errorMessage;
this.carList = carList;
}


@Override
public String toString() {
return "ApiResponseGetCarList{" +
"result='" + result + '\'' +
", code='" + code + '\'' +
", errorMessage='" + errorMessage + '\'' +
", carList=" + carList +
'}';
}
}

Tạo class ApiResponseGetCar.java

Tên file: ApiResponseGetCar.java

Nơi lưu file: java/com/vantinviet/adayroionline/ApiResponse

Nội dung file:

package com.vantinviet.adayroionline.ApiResponse;

import com.google.gson.annotations.SerializedName;
import com.vantinviet.adayroionline.viewobject.Car;


public class ApiResponseGetCar {


@SerializedName("result")
public String result;

@SerializedName("code")
public String code;

@SerializedName("errorMessage")
public String errorMessage;

@SerializedName("data")
public Car car;

public ApiResponseGetCar(String result, String code, String errorMessage, Car car) {
this.result = result;
this.code = code;
this.errorMessage = errorMessage;
this.car = car;
}


@Override
public String toString() {
return "ApiResponseGetCar{" +
"result='" + result + '\'' +
", code='" + code + '\'' +
", errorMessage='" + errorMessage + '\'' +
", car=" + car +
'}';
}
}

Xây dựng lớp ApiResponseGetDeleteCar.java

Tên file: ApiResponseGetDeleteCar.java

Nơi lưu file: java/com/vantinviet/adayroionline/ApiResponse

Nội dung file:

package com.vantinviet.adayroionline.ApiResponse;

import com.google.gson.annotations.SerializedName;

/**
* Created by adayroionline.online on 12/6/17.
* Contact Email : nguyendinhcuong@gmail.com
*/

public class ApiResponseGetDeleteCar {


@SerializedName("result")
public String result;

@SerializedName("code")
public String code;

@SerializedName("errorMessage")
public String errorMessage;


public ApiResponseGetDeleteCar(String result, String code, String errorMessage) {
this.result = result;
this.code = code;
this.errorMessage = errorMessage;
}


@Override
public String toString() {
return "ApiResponseGetDeleteCar{" +
"result='" + result + '\'' +
", code='" + code + '\'' +
", errorMessage='" + errorMessage + '\'' +
'}';
}
}

Bổ xung api vào PSApiService

Bổ xung xuống cuối nội dung sau vào  interface PSApiService

@Headers({"Authorization:" + Config.API_KEY})
@GET("/api/cars/limit/{limit}/start/{offset}")
Call<ApiResponseGetCarList> getAllCar(@Path("limit") String limit, @Path("offset") String offset);
@FormUrlEncoded
@Headers({"Authorization:" + Config.API_KEY})
@POST("/api_task/car.update_car")
LiveData<ApiResponse<ApiResponseGetCar>> updateCar(
@Query("car_id") String car_id,
@Field("car_name") String car_name,
@Field("car_color") String car_color,
@Field("car_model") String car_model,
@Field("car_description") String car_description


);

@FormUrlEncoded
@Headers({"Authorization:" + Config.API_KEY})
@POST("/api_task/car.insert_car")
LiveData<ApiResponse<ApiResponseGetCar>> insertCar(
@Field("car_name") String car_name,
@Field("car_color") String car_color,
@Field("car_model") String car_model,
@Field("car_description") String car_description


);


@Headers({"Authorization:" + Config.API_KEY, "Content-Type: application/json"})
@GET("api/car/{id}")
LiveData<ApiResponse<ApiResponseGetCar>> getCarById(@Path("id") String id);
@Headers({"Authorization:" + Config.API_KEY, "Content-Type: application/json"})
@POST("/api_task/car.delete_car")
Call<ApiResponseGetDeleteCar> getDeleteCar(
@Query("car_id") String carId
);

 

Xây dựng API trên server

Để xây dựng API quản lý car bạn nhấn vào đây để xem nhé

Bổ xung ngôn ngữ trong string.xml

1.Mở file res/values/strings.xml

Thêm các dòng sau vào thẻ resources

<string name="car_detail_error_message">error message</string>
<string name="Cars">Cars</string>
<string name="menu_car_list">car list</string>
<string name="car_form_title">car form</string>
<string name="car_name">car name</string>
<string name="color">color</string>
<string name="model">model</string>
<string name="description">description</string>
<string name="Cancel" />
<string name="error_message__blank_color">please input color</string>
<string name="error_message__blank_model">please input color</string>
<string name="car_detail__title">Car detail</string>
<string name="str_delete_car_success">Delete car success</string>
<string name="edit">Edit</string>

2.Mở file res/values-vi/strings.xml

Bổ xung dòng sau
<string name="car_detail_error_message">Có lỗi phát sinh</string>
<string name="Cars">Cars</string>
<string name="menu_car_list">car list</string>
<string name="car_form_title">car form</string>
<string name="car_name">car name</string>
<string name="color">color</string>
<string name="model">model</string>
<string name="description">description</string>
<string name="Cancel" />
<string name="error_message__blank_color">please input color</string>
<string name="error_message__blank_model">please input color</string>
<string name="car_detail__title">Car detail</string>
<string name="str_delete_car_success">Delete car success</string>
<string name="edit">Edit</string>


Bổ xung sự kiện chuyển trang cho menu trong MainActivity.java

Mở file java/com/vantinviet/adayroionline/ui/dashboard/MainActivity.java

 

Bổ xung đoạn code sau  trong  câu lệnh switch (item.getItemId())

Tìm phương thức openFragment như hình
anh36
Bổ xung trong switch (menuId) 

case R.id.nav_carList:
setToolbarText(binding.toolbar, getString(R.string.menu_car_list));
navigationController.navigateToCarList(this);
Utils.psLog("nav_car_list");
break;

Xây dựng thanh điều hướng NavigationController

Mở file java/com/vantinviet/adayroionline/ui/common/NavigationController.java

Import các phần sau vào nhé

import com.vantinviet.adayroionline.ui.car.detail.CarDetailActivity;
import com.vantinviet.adayroionline.ui.car.detail.CarDetailFragment;
import com.vantinviet.adayroionline.ui.car.form.CarFormActivity;
import com.vantinviet.adayroionline.ui.car.list.CarListActivity;

Sau khi bổ xung import xong
chúng ta cần bổ xung các phương thức sau vào class NavigationController
public void navigateToCarList(Activity activity) {
Intent intent = new Intent(activity, CarListActivity.class);
activity.startActivity(intent);
}
public void navigateToCarDetailActivity(Activity activity) {
Intent intent = new Intent(activity, CarDetailActivity.class);
activity.startActivity(intent);
}
public void navigateToCarFormActivity(Activity activity,String carId) {
Intent intent = new Intent(activity, CarFormActivity.class);
if (!carId.equals(Constants.NO_DATA)) {
intent.putExtra(Constants.CAR_ID, carId);
}

activity.startActivity(intent);
}

Tạo menu item cars trong menu_drawer.xml

Mở file res/menu/menu_drawer.xml

Tìm thẻ như hình

anh41

Bỏ xung dòng sau 

<item
android:id="@+id/nav_carList"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:title="@string/Cars" />

Xây dựng lớp CarRepository.java

Tên file: CarRepository.java

Nơi lưu file: java/com/vantinviet/adayroionline/repository/car (chú ý bạn phải tao sub package nhé)

Nội dung file:

package com.vantinviet.adayroionline.repository.car;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;

import com.vantinviet.adayroionline.ApiResponse.ApiResponseGetCarList;
import com.vantinviet.adayroionline.ApiResponse.ApiResponseGetCar;
import com.vantinviet.adayroionline.ApiResponse.ApiResponseGetDeleteCar;
import com.vantinviet.adayroionline.ApiResponse.ApiResponseUser;
import com.vantinviet.adayroionline.AppExecutors;
import com.vantinviet.adayroionline.Lib.Factory;
import com.vantinviet.adayroionline.api.ApiResponse;
import com.vantinviet.adayroionline.api.PSApiService;
import com.vantinviet.adayroionline.db.CarDao;
import com.vantinviet.adayroionline.db.PSCoreDb;
import com.vantinviet.adayroionline.repository.common.NetworkBoundResource;
import com.vantinviet.adayroionline.repository.common.PSRepository;
import com.vantinviet.adayroionline.utils.AbsentLiveData;
import com.vantinviet.adayroionline.utils.Utils;
import com.vantinviet.adayroionline.viewobject.Car;
import com.vantinviet.adayroionline.viewobject.User;
import com.vantinviet.adayroionline.viewobject.UserLogin;
import com.vantinviet.adayroionline.viewobject.common.Resource;

import java.io.IOException;
import java.util.List;

import javax.inject.Inject;
import javax.inject.Singleton;

import retrofit2.Response;

@Singleton
public class CarRepository extends PSRepository {

//region variable
private final CarDao carDao;
//end region

//region constructor
@Inject
CarRepository(PSApiService psApiService, AppExecutors appExecutors, PSCoreDb db, CarDao carDao) {
super(psApiService, appExecutors, db);
this.carDao = carDao;
}


//region User Repository Functions for ViewModel
public LiveData<Resource<List<Car>>> getAllCar(String limit, String offset) {

final MutableLiveData<Resource<List<Car>>> statusLiveData = new MutableLiveData<>(); // To update the status to the listener

appExecutors.networkIO().execute(() -> {

try {

// Call the API Service
Response<ApiResponseGetCarList> response =
psApiService.getAllCar(limit,offset).execute();
// Wrap with APIResponse Class
ApiResponse<ApiResponseGetCarList> apiResponseGetCarList = new ApiResponse<>(response);
Utils.psLog("apiResponseGetCarList",apiResponseGetCarList.toString());
if(apiResponseGetCarList.body.code.equals("101")){
statusLiveData.postValue(Resource.error(apiResponseGetCarList.body.errorMessage, null));
return;
}
// If response is successful
if (apiResponseGetCarList.isSuccessful()) {

db.beginTransaction();

try {
List<Car> carList=apiResponseGetCarList.body.carList;
carDao.deleteAll();
carDao.insertAll(carList);
db.setTransactionSuccessful();

} catch (Exception e) {
Utils.psErrorLog("Error in doing car of recent car list.", e);
} finally {
db.endTransaction();
}
statusLiveData.postValue(Resource.success(apiResponseGetCarList.body.carList));
} else {
statusLiveData.postValue(Resource.error(apiResponseGetCarList.errorMessage, null));
}

} catch (IOException e) {
statusLiveData.postValue(Resource.error(e.getMessage(), null));
}

});
return statusLiveData;

}




// Get next page transaction list
public LiveData<Resource<Boolean>> getNextPageCarList(String limit,String offset) {

final MutableLiveData<Resource<Boolean>> statusLiveData = new MutableLiveData<>(); // To update the status to the listener

appExecutors.networkIO().execute(() -> {

try {

// Call the API Service
Response<ApiResponseGetCarList> response =
psApiService.getAllCar(limit,offset).execute();


// Wrap with APIResponse Class
ApiResponse<ApiResponseGetCarList> apiResponseGetCarList = new ApiResponse<>(response);
if(apiResponseGetCarList.body.code.equals("101")){
statusLiveData.postValue(Resource.error(apiResponseGetCarList.body.errorMessage, null));
return;
}
// If response is successful
if (apiResponseGetCarList.isSuccessful()) {

db.beginTransaction();

try {

carDao.deleteAll();
carDao.insertAll(apiResponseGetCarList.body.carList);
db.setTransactionSuccessful();

} catch (Exception e) {
Utils.psErrorLog("Error in doing transaction of recent car list.", e);
} finally {
db.endTransaction();
}
} else {
statusLiveData.postValue(Resource.error(apiResponseGetCarList.errorMessage, null));
}

} catch (IOException e) {
statusLiveData.postValue(Resource.error(e.getMessage(), null));
}

});
return statusLiveData;

}
public LiveData<Resource<Car>> getCarById(String carId) {

return new NetworkBoundResource<Car, ApiResponseGetCar>(appExecutors) {

@Override
protected void saveCallResult(@NonNull ApiResponseGetCar apiResponseGetCar) {
Utils.psLog("SaveCallResult of recent car.");

db.beginTransaction();

try {

carDao.deleteCarById(carId);
carDao.insert(apiResponseGetCar.car);
db.setTransactionSuccessful();

} catch (Exception e) {
Utils.psErrorLog("Error in doing car of list.", e);
} finally {
db.endTransaction();
}
}

@Override
protected boolean shouldFetch(@Nullable Car data) {

// Recent news always load from server
return connectivity.isConnected();

}

@NonNull
@Override
protected LiveData<Car> loadFromDb() {
Utils.psLog("Load car From Db");

return carDao.getCarById(carId);

}

@NonNull
@Override
protected LiveData<ApiResponse<ApiResponseGetCar>> createCall() {
Utils.psLog("Call API Service to get car by id.");

return psApiService.getCarById(carId);

}

@Override
protected void onFetchFailed(String message) {
Utils.psLog("Fetch Failed (getCar) : " + message);
}

}.asLiveData();
}
public LiveData<Resource<ApiResponseGetCar>> updateCar(Car car) {

return new NetworkBoundResource<ApiResponseGetCar, ApiResponseGetCar>(appExecutors) {

String carId = "";
private ApiResponseGetCar resultsDb;

@Override
protected void saveCallResult(@NonNull ApiResponseGetCar apiResponseGetCar) {
Utils.psLog("SaveCallResult of update car.");

db.beginTransaction();
try {

if(apiResponseGetCar.result.equals("success")) {

// set User id
carId = apiResponseGetCar.car.id;

// update car data
carDao.update(apiResponseGetCar.car);



db.setTransactionSuccessful();

}
resultsDb = apiResponseGetCar;

} catch (Exception e) {
Utils.psErrorLog("Error in doing transaction of update car.", e);
} finally {
db.endTransaction();
}
}

@Override
protected boolean shouldFetch(@Nullable ApiResponseGetCar data) {
// for car update, always should fetch
return connectivity.isConnected();
}

@NonNull
@Override
protected LiveData<ApiResponseGetCar> loadFromDb() {
if(carId == null || carId.equals("")) {
return AbsentLiveData.create();
}

return new LiveData<ApiResponseGetCar>() {
@Override
protected void onActive() {
super.onActive();
setValue(resultsDb);
}
};
}

@NonNull
@Override
protected LiveData<ApiResponse<ApiResponseGetCar>> createCall() {
Utils.psLog("Call API Service to update car.");
if (car.id != null && !car.id.isEmpty()) {
return psApiService.updateCar(car.id, car.carName, car.carColor, car.carModel, car.carDescription);
}else {
return psApiService.insertCar(car.carName, car.carColor, car.carModel, car.carDescription);
}
}

@Override
protected void onFetchFailed(String message) {

Utils.psLog("Fetch Failed (updateUser)." + message);
}
}.asLiveData();
}
public LiveData<Resource<Boolean>> getCarDelete(String carId) {

final MutableLiveData<Resource<Boolean>> statusLiveData = new MutableLiveData<>();

appExecutors.networkIO().execute(() -> {

Response<ApiResponseGetDeleteCar> response;

try {

response = psApiService.getDeleteCar(carId).execute();

ApiResponse<ApiResponseGetDeleteCar> apiResponse = new ApiResponse<>(response);

if (apiResponse.isSuccessful() && apiResponse.body.code.equals("200")) {
statusLiveData.postValue(Resource.success(true));
} else {
statusLiveData.postValue(Resource.error(apiResponse.body.errorMessage, null));
}

} catch (IOException e) {
statusLiveData.postValue(Resource.error(e.getMessage(), null));
}

});

return statusLiveData;

}


}

Xây dựng lớp CarViewModel.java

Tên file: CarViewModel.java

Nơi lưu file: java/com/vantinviet/adayroionline/viewmodel/car (chú ý bạn cần tạo các sub package nhé)

Nội dung file:

 

package com.vantinviet.adayroionline.viewmodel.car;
import com.vantinviet.adayroionline.ApiResponse.ApiResponseGetCar;
import com.vantinviet.adayroionline.ApiResponse.ApiResponseUser;
import com.vantinviet.adayroionline.Config;
import com.vantinviet.adayroionline.repository.car.CarRepository;
import com.vantinviet.adayroionline.utils.AbsentLiveData;
import com.vantinviet.adayroionline.utils.Utils;
import com.vantinviet.adayroionline.viewmodel.common.PSViewModel;
import com.vantinviet.adayroionline.viewmodel.order.OrderProductListViewModel;
import com.vantinviet.adayroionline.viewobject.Car;
import com.vantinviet.adayroionline.viewobject.User;
import com.vantinviet.adayroionline.viewobject.common.Resource;

import java.util.List;

import javax.inject.Inject;

import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;

public class CarViewModel extends PSViewModel {
private final LiveData<Resource<List<Car>>> carsData;
private MutableLiveData<TmpDataHolder> carsObj = new MutableLiveData<>();
private MutableLiveData<Car> updateCarObj = new MutableLiveData<>();
private LiveData<Resource<ApiResponseGetCar>> updateCarData;
private final LiveData<Resource<Boolean>> deletedCar;
private final LiveData<Resource<Boolean>> nextPageCarsData;
private MutableLiveData<TmpDataHolder> nextPageCarsObj = new MutableLiveData<>();

private final LiveData<Resource<Car>> carByIdData;
private MutableLiveData<CarByIdTmpDataHolder> carByIdObj = new MutableLiveData<>();
private MutableLiveData<CarViewModel.TmpDeleteCarDataHolder> tmpDeleteCarDataHolder = new MutableLiveData<>();

public Car car;

@Inject
CarViewModel(CarRepository carRepository) {

carsData = Transformations.switchMap(carsObj, obj -> {

if (obj == null) {
return AbsentLiveData.create();
}

return carRepository.getAllCar(obj.limit, obj.offset);

});

nextPageCarsData = Transformations.switchMap(nextPageCarsObj, obj -> {

if (obj == null) {
return AbsentLiveData.create();
}

return carRepository.getNextPageCarList( obj.limit, obj.offset);

});

carByIdData = Transformations.switchMap(carByIdObj, obj -> {

if (obj == null) {
return AbsentLiveData.create();
}

return carRepository.getCarById(obj.id);

});
// Update User
updateCarData = Transformations.switchMap(updateCarObj, obj -> {
if (obj == null) {
return AbsentLiveData.create();
}
Utils.psLog("UserViewModel : updateUserData");
return carRepository.updateCar(updateCarObj.getValue());
});
deletedCar = Transformations.switchMap(tmpDeleteCarDataHolder, tmpDeleteOrderDataHolder -> {
if (tmpDeleteOrderDataHolder == null) {
return AbsentLiveData.create();
}
return carRepository.getCarDelete(tmpDeleteCarDataHolder.getValue().carId);
});


}

public void setCarsObj(String limit, String offset) {
TmpDataHolder tmpDataHolder = new TmpDataHolder(limit, offset);

this.carsObj.setValue(tmpDataHolder);
}

public LiveData<Resource<List<Car>>> getCarsData() {
return carsData;
}

public void setNextPageCarsObj(String limit, String offset) {
TmpDataHolder tmpDataHolder = new TmpDataHolder(limit, offset);

this.nextPageCarsObj.setValue(tmpDataHolder);
}

public LiveData<Resource<Boolean>> getNextPageCarsData() {
return nextPageCarsData;
}

public void setCarByIdObj(String id) {
CarByIdTmpDataHolder carByIdTmpDataHolder = new CarByIdTmpDataHolder(id);

this.carByIdObj.setValue(carByIdTmpDataHolder);
}

public LiveData<Resource<Car>> getCarByIdData() {
return carByIdData;
}
public void setUpdateCarObj(Car car) {
updateCarObj.setValue(car);
}
public LiveData<Resource<ApiResponseGetCar>> getUpdateCarData() {

return updateCarData;
}
public LiveData<Resource<Boolean>> getDeletedCar() {
return deletedCar;
}
public void setDeleteCarObj(String carId) {
if (!isLoading) {
CarViewModel.TmpDeleteCarDataHolder tmpDeleteCarDataHolder = new CarViewModel.TmpDeleteCarDataHolder();
tmpDeleteCarDataHolder.carId = carId;
this.tmpDeleteCarDataHolder.setValue(tmpDeleteCarDataHolder);
// start loading
setLoadingState(true);
}
}
class TmpDataHolder {

String limit, offset;

public TmpDataHolder(String limit, String offset) {
this.limit = limit;
this.offset = offset;
}
}

class CarByIdTmpDataHolder {

String id;

private CarByIdTmpDataHolder(String id) {
this.id = id;
}
}
class TmpDeleteCarDataHolder {
public String carId = "";
public Boolean isConnected = false;
}
}

Bổ xung CarViewModel vào ViewModelModule

Mở file java/com/vantinviet/adayroionline/di/ViewModelModule.java

import CarViewModel

import com.vantinviet.adayroionline.viewmodel.car.CarViewModel;

Bổ xung đoạn code sau vào cuối class ViewModelModule

@Binds
@IntoMap
@ViewModelKey(CarViewModel.class)
abstract ViewModel bindCarViewModel(CarViewModel carViewModel);
anh40

Tạo layout item_car_list_adapter.xml

Tên file: item_car_list_adapter.xml

Nơi lưu file: res/layout

Nội dung file:

 

<layout 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">

<data>

<variable
name="car"
type="com.vantinviet.adayroionline.viewobject.Car" />

</data>

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="visible">


<androidx.cardview.widget.CardView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="4dp"
android:layout_marginBottom="4dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

<ImageView
android:id="@+id/carImageView"
android:layout_width="140dp"
android:layout_height="133dp"
android:layout_marginStart="4dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:contentDescription="@string/image_default__image"
android:scaleType="centerCrop"
app:imageUrl="@{car.defaultPhoto.imgPath}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/nameTextView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/default_image" />

<TextView
android:id="@+id/carNameTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:text="@{car.carName}"
android:textAlignment="viewStart"
android:textColor="@color/text__primary"
android:textSize="@dimen/font_body_size"
app:font='@{"normal"}'
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/carImageView"
app:layout_constraintTop_toTopOf="parent"
tools:text="Comment" />

<TextView
android:id="@+id/carModelTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@{car.carModel}"
android:textColor="@color/text__primary_light"
android:textSize="@dimen/font_body_s_size"
app:font='@{"normal"}'
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/view59"
tools:text="date" />

<View
android:id="@+id/view59"
android:layout_width="0dp"
android:layout_height="4dp"
android:layout_marginStart="8dp"
android:background="#fff"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toEndOf="@+id/carImageView"
app:layout_constraintTop_toBottomOf="@+id/carNameTextView"
tools:ignore="MissingConstraints" />

<View
android:id="@+id/view60"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginStart="8dp"
android:layout_marginBottom="24dp"
android:background="#fff"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toEndOf="@+id/carImageView"
app:layout_constraintTop_toBottomOf="@+id/carModelTextView"
tools:ignore="MissingConstraints" />

</androidx.constraintlayout.widget.ConstraintLayout>

</androidx.cardview.widget.CardView>

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Xây dựng class CarListAdapter.java

Tên file: CarListAdapter.java

Nơi lưu file: java/com/vantinviet/adayroionline/ui/car/list/adapter (Bạn cần tạo các sub package nhé)

Nội dung file:

package com.vantinviet.adayroionline.ui.car.list.adapter;
/*
Đưa vào một số thư viện để code, trong quá trình code các bạn chú ý không phải copy nguyên và cũng sẽ có những thứ các bạn cần và không cần
*/
import android.view.LayoutInflater;
import android.view.ViewGroup;

import androidx.databinding.DataBindingComponent;
import androidx.databinding.DataBindingUtil;


import com.vantinviet.adayroionline.R;
/*
Các bạn lưu ý ở đây các bạn nhìn thấy có hai cái car nhưng các bạn chỉ đối tên cái car bôi đậm ví dụ CarCarBinding, CarPetBinding
*/
import com.vantinviet.adayroionline.databinding.ItemCarListAdapterBinding; // Car này là một đối tượng layout lát nữa chúng ta sẽ tạo đối tượng layout này. nếu nó đang lỗi các bạn cứ để nguyên đó không cần phải lo lắng
import com.vantinviet.adayroionline.ui.car.list.CarListFragment;
import com.vantinviet.adayroionline.ui.common.DataBoundListAdapter;
import com.vantinviet.adayroionline.ui.common.DataBoundViewHolder;
import com.vantinviet.adayroionline.utils.Objects;
import com.vantinviet.adayroionline.utils.Utils;
import com.vantinviet.adayroionline.viewobject.Car; //Đối tượng view object này chúng ta đã tạo ra ở bước 2 rồi, giờ cũng cần phải đưa vào

public class CarListAdapter extends DataBoundListAdapter<Car /* Truyền đối tượng car vào */, ItemCarListAdapterBinding /*Nhớ đọc kỹ ở phần trên để thay thế cho đúng*/> {

private final androidx.databinding.DataBindingComponent dataBindingComponent;
private final CarClickCallback carClickCallback; // khai báo một sự kiện click (nhấn vào danh sách)
private DiffUtilDispatchedInterface diffUtilDispatchedInterface = null;


public CarListAdapter(DataBindingComponent dataBindingComponent,
CarClickCallback callback,/*tham số callback để khi người dùng nhấn vào*/CarListFragment carListFragment) {
this.dataBindingComponent = dataBindingComponent;
this.carClickCallback = callback; //Tạo một đối tượng click callback

}
//Một số phương thức ghi đè cần có
@Override
protected ItemCarListAdapterBinding createBinding(ViewGroup parent) {
ItemCarListAdapterBinding binding = DataBindingUtil
.inflate(LayoutInflater.from(parent.getContext()),
R.layout.item_car_list_adapter/* car_car_list_adapter đã được tạo ở bước 5.2 */, parent, false,
dataBindingComponent);

binding.getRoot().setOnClickListener(v -> {
Car car = binding.getCar();
if (car != null && carClickCallback != null) {
carClickCallback.onClick(car);
}
});
return binding;
}

// For general animation
@Override
public void bindView(DataBoundViewHolder<ItemCarListAdapterBinding> holder, int position) {
super.bindView(holder, position);

}

@Override
protected void dispatched() {
if (diffUtilDispatchedInterface != null) {
diffUtilDispatchedInterface.onDispatched();
}
}

@Override
protected void bind(ItemCarListAdapterBinding binding, Car car) {
//Bây giờ chúng ta sẽ bắt đầu hiển thị dữ liệu cho một car. ví dụ trong trường ví dụ trong trường hợp này chúng ta chỉ cần hiển thị car_name. như vậy các bạn sẽ phải có thuộc tính car_name, thì bạn cần phải bổ sung cho view object ở bước 2 nhé
//Utils.psLog("car.car_name",car.car_name);
//set giá trị cho text
Utils.psLog("car.carName",car.carName);
//binding.carNameTextView.setText("sdfdsfds");
binding.setCar(car);
Utils.psLog("some log","content log");


}

/*
Một số phương thức này để kiểm tra khi cần update dữ liệu hiển thị
*/
@Override
protected boolean areItemsTheSame(Car oldItem, Car newItem) {
return Objects.equals(oldItem.id, newItem.id)
&& oldItem.carName.equals(newItem.carName);
}

@Override
protected boolean areContentsTheSame(Car oldItem, Car newItem) {
return Objects.equals(oldItem.id, newItem.id)
&& oldItem.carName.equals(newItem.carName);
}

public interface CarClickCallback {
void onClick(Car car);
}


}

Tạo file fragment_car_list.xml

Tên file: fragment_car_list.xml

Nơi lưu file: res/layout

Nội dung file:

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

<data>

<variable
name="loadingMore"
type="boolean" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
android:focusableInTouchMode="true">

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">

<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/carListRecyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="8dp"
android:clipToPadding="false"
android:orientation="vertical"
android:paddingStart="@dimen/space_20"
android:paddingTop="@dimen/space_16"
android:paddingEnd="@dimen/space_20"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toTopOf="@+id/progressBar3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

<ProgressBar
android:id="@+id/progressBar3"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:indeterminate="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:visibleGone="@{loadingMore}" />

</androidx.constraintlayout.widget.ConstraintLayout>

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/space_16"
android:layout_marginBottom="@dimen/space_64"
android:gravity="center_horizontal"
android:orientation="vertical"
tools:layout_editor_absoluteX="338dp"
tools:layout_editor_absoluteY="403dp">


<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/AddCarButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:clickable="true"
android:contentDescription="@string/Cancel"
android:focusable="true"
android:src="@drawable/baseline_upload_grey_24"
android:tint="@color/md_white_1000"
app:backgroundTint="@color/global__primary_dark"
app:fabSize="normal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

Tạo file CarListFragment.java

Tên file: CarListFragment.java

Nơi lưu file: java/com/vantinviet/adayroionline/ui/car/list

Nội dung file:

//Tên gói
package com.vantinviet.adayroionline.ui.car.list;

import android.app.ProgressDialog;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.databinding.DataBindingUtil;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModelProviders;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.google.android.material.snackbar.Snackbar;
import com.vantinviet.adayroionline.Config;
import com.vantinviet.adayroionline.R;
import com.vantinviet.adayroionline.binding.FragmentDataBindingComponent;
//import Fragment_Car_List
//import CarListAdapter
import com.vantinviet.adayroionline.databinding.FragmentCarListBinding;
import com.vantinviet.adayroionline.ui.car.list.adapter.CarListAdapter;
import com.vantinviet.adayroionline.ui.common.DataBoundListAdapter;
import com.vantinviet.adayroionline.ui.common.PSFragment;
import com.vantinviet.adayroionline.utils.AutoClearedValue;
import com.vantinviet.adayroionline.utils.PSDialogMsg;
import com.vantinviet.adayroionline.utils.Utils;
import com.vantinviet.adayroionline.viewmodel.car.CarViewModel;
import com.vantinviet.adayroionline.viewobject.Car;
import com.vantinviet.adayroionline.viewobject.common.Resource;
import com.vantinviet.adayroionline.viewobject.common.Status;

import java.util.List;

public class CarListFragment extends PSFragment implements DataBoundListAdapter.DiffUtilDispatchedInterface {

private final androidx.databinding.DataBindingComponent dataBindingComponent = new FragmentDataBindingComponent(this);
private CarViewModel carViewModel;
private PSDialogMsg psDialogMsg;
private List<Car> carList;
@VisibleForTesting
private AutoClearedValue<FragmentCarListBinding> fragmentCarListBindingAutoClearedValue;
private AutoClearedValue<CarListAdapter> carListAdapterAutoClearedValue;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
//set car fragment sử dụng layout fragment_car_list
FragmentCarListBinding fragmentCarListBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_car_list, container, false, dataBindingComponent);

fragmentCarListBindingAutoClearedValue = new AutoClearedValue<>(this, fragmentCarListBinding);

return fragmentCarListBindingAutoClearedValue.get().getRoot();

}
//Khởi tạo các ui
@Override
protected void initUIAndActions() {
psDialogMsg = new PSDialogMsg(getActivity(), false);
//kiểm tra khi có sự kiện scroll của carListRecyclerView
fragmentCarListBindingAutoClearedValue.get().carListRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
//Khi người dùng kéo xong
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
LinearLayoutManager layoutManager = (LinearLayoutManager)
recyclerView.getLayoutManager();

if (layoutManager != null) {

int lastPosition = layoutManager
.findLastVisibleItemPosition();

if (lastPosition == carListAdapterAutoClearedValue.get().getItemCount() - 1) {

if (!fragmentCarListBindingAutoClearedValue.get().getLoadingMore() && !carViewModel.forceEndLoading) {

carViewModel.loadingDirection = Utils.LoadingDirection.bottom;

int limit = Config.LIST_CARS_COUNT;

carViewModel.start = carViewModel.start + limit;

carViewModel.setLoadingState(true);

carViewModel.setNextPageCarsObj(String.valueOf(Config.LIST_CARS_COUNT), String.valueOf(carViewModel.start));
}
}
}
}
});


fragmentCarListBindingAutoClearedValue.get().swipeRefresh.setColorSchemeColors(getResources().getColor(R.color.view__primary_line));
fragmentCarListBindingAutoClearedValue.get().swipeRefresh.setProgressBackgroundColorSchemeColor(getResources().getColor(R.color.global__primary));
fragmentCarListBindingAutoClearedValue.get().swipeRefresh.setOnRefreshListener(() -> {

carViewModel.loadingDirection = Utils.LoadingDirection.top;

// reset carViewModel.offset

carViewModel.start = 0;

// reset carViewModel.forceEndLoading
carViewModel.forceEndLoading = false;

carViewModel.setCarsObj(String.valueOf(Config.LIST_CARS_COUNT), String.valueOf(carViewModel.start));

// update live data

});
fragmentCarListBindingAutoClearedValue.get().AddCarButton.setOnClickListener(v -> {
navigationController.navigateToCarFormActivity(getActivity(),"");
});



}

@Override
protected void initViewModels() {

carViewModel = ViewModelProviders.of(this, viewModelFactory).get(CarViewModel.class);

}

@Override
protected void initAdapters() {

CarListAdapter nvAdapter = new CarListAdapter(dataBindingComponent,car -> navigationController.navigateToCarDetailActivity/*Hiện tại hàm này chưa được xây dựng, chúng ta sẽ làm ở bước này nhấn vào đây để xem [Chú ý là bước này chúng ta sẽ làm sau nhé] */(CarListFragment.this.getActivity(), car.id), this);

this.carListAdapterAutoClearedValue = new AutoClearedValue<>(this, nvAdapter);
fragmentCarListBindingAutoClearedValue.get().carListRecyclerView.setAdapter(carListAdapterAutoClearedValue.get());
new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
// this method is called
// when the item is moved.
return false;
}

@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
// this method is called when we swipe our item to right direction.
// on below line we are getting the item at a particular position.
Car car = carList.get(viewHolder.getAdapterPosition());

// below line is to get the position
// of the item at that position.
int position = viewHolder.getAdapterPosition();
psDialogMsg.showConfirmDialog("Bạn có chắc chăn muốn xóa không ?","Chắc chắn","Hủy");
psDialogMsg.show();

psDialogMsg.okButton.setOnClickListener(view -> {

psDialogMsg.cancel();

// this method is called when item is swiped.
// below line is to remove item from our array list.
carList.remove(viewHolder.getAdapterPosition());
// below line is to notify our item is removed from adapter.
carListAdapterAutoClearedValue.get().notifyItemRemoved(viewHolder.getAdapterPosition());
// below line is to display our snackbar with action.
Snackbar.make(fragmentCarListBindingAutoClearedValue.get().carListRecyclerView, car.carName, Snackbar.LENGTH_LONG).setAction("Undo", new View.OnClickListener() {
@Override
public void onClick(View v) {
// adding on click listener to our action of snack bar.
// below line is to add our item to array list with a position.
carList.add(position, car);

// below line is to notify item is
// added to our adapter class.
carListAdapterAutoClearedValue.get().notifyItemInserted(position);
}
}).show();
carViewModel.setDeleteCarObj(car.id);
});
psDialogMsg.cancelButton.setOnClickListener(view -> {
psDialogMsg.cancel();
carList.add(position, car);
carListAdapterAutoClearedValue.get().notifyItemInserted(position);
});
}
// at last we are adding this
// to our recycler view.
}).attachToRecyclerView(fragmentCarListBindingAutoClearedValue.get().carListRecyclerView);
}
//Khởi tạo dữ liệu
@Override
protected void initData() {
// bắt đầu lấy dữ liệu
carViewModel.setCarsObj(String.valueOf(Config.LIST_CARS_COUNT), String.valueOf(carViewModel.start));
//Quan sát khi có dữ liệu được lấy về
carViewModel.getCarsData().observe(this, result -> {

if (result != null) {
switch (result.status) {
//Nếu lấy dữ liệu về thành công
case SUCCESS:
carList=result.data;
replaceCarList(result.data);
carViewModel.setLoadingState(false);
break;
//nếu vẫn đang trong quá trình tải
case LOADING:
replaceCarList(result.data);
break;
//nếu phát sinh một lỗi bất kỳ
case ERROR:

carViewModel.setLoadingState(false);
break;
}
}

});
//Quan sát khi người dùng kéo scroll xuống dưới
carViewModel.getNextPageCarsData().observe(this, state -> {
if (state != null) {
if (state.status == Status.ERROR) {

carViewModel.setLoadingState(false);
carViewModel.forceEndLoading = true;
}
}
});


carViewModel.getLoadingState().observe(this, loadingState -> {

fragmentCarListBindingAutoClearedValue.get().setLoadingMore(carViewModel.isLoading);

if (loadingState != null && !loadingState) {
fragmentCarListBindingAutoClearedValue.get().swipeRefresh.setRefreshing(false);
}

});
LiveData<Resource<Boolean>> delete_car = carViewModel.getDeletedCar();
delete_car.observe(this, status_Resource -> {
if (status_Resource != null) {
psDialogMsg.showSuccessDialog(getString(R.string.str_delete_car_success),"Ok");
psDialogMsg.show();
}
});
}
//Thực hiện việc cập nhật lại dữ liệu car
private void replaceCarList(List<Car> carList) {
Utils.psLog("carList",carList.toString());
this.carListAdapterAutoClearedValue.get().replace(carList);
fragmentCarListBindingAutoClearedValue.get().executePendingBindings();
}


@Override
public void onDispatched() {
if (carViewModel.loadingDirection == Utils.LoadingDirection.top) {

if (fragmentCarListBindingAutoClearedValue.get().carListRecyclerView != null) {

LinearLayoutManager layoutManager = (LinearLayoutManager)
fragmentCarListBindingAutoClearedValue.get().carListRecyclerView.getLayoutManager();

if (layoutManager != null) {
layoutManager.scrollToPosition(0);
}
}
}
}
}

Tạo layout activity_car_list.xml

Tên file: activity_car_list.xml

Nơi lưu file: res/layout

Nội dung file:

<?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">

<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/main_content"
android:layout_height="match_parent"
android:layout_width="match_parent">

<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

<androidx.appcompat.widget.Toolbar
style="@style/ToolBarStyle.Event"
android:background="@color/global__primary"
android:id="@+id/toolbar"
android:layout_height="?android:attr/actionBarSize"
android:layout_width="match_parent"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

</com.google.android.material.appbar.AppBarLayout>

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">

<FrameLayout
android:id="@+id/content_frame"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">

</FrameLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

</layout>

Tạo file CarListActivity.java

Tên file: CarListActivity.java

Nơi lưu file: java/com/vantinviet/adayroionline/ui/car/list (bạn cần tạo các sub package theo cấu trúc thư mục như trên nhé)

Nội dung file:

package com.vantinviet.adayroionline.ui.car.list;

import android.os.Bundle;

import com.vantinviet.adayroionline.R;
import com.vantinviet.adayroionline.databinding.ActivityCarListBinding;
import com.vantinviet.adayroionline.ui.common.PSAppCompactActivity;

import androidx.databinding.DataBindingUtil;

public class CarListActivity extends PSAppCompactActivity {

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

ActivityCarListBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_car_list);

initUI(binding);

}

private void initUI(ActivityCarListBinding binding) {

// Toolbar
initToolbar(binding.toolbar, "Danh sách xe");

// setup Fragment

setupFragment(new CarListFragment());

}
}

Tạo layout activity_car_detail.xml

Tên file: activity_car_detail.xml

Nơi lưu file: res/layout

Nội dung file:

<?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">

<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/main_content"
android:layout_height="match_parent"
android:layout_width="match_parent">

<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

<androidx.appcompat.widget.Toolbar
style="@style/ToolBarStyle.Event"
android:background="@color/global__primary"
android:id="@+id/toolbar"
android:layout_height="?android:attr/actionBarSize"
android:layout_width="match_parent"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

</com.google.android.material.appbar.AppBarLayout>

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">

<FrameLayout
android:id="@+id/content_frame"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/md_white_1000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">

</FrameLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

</layout>

Tạo layout fragment_car_detail.xml

Tên file: fragment_car_detail.xml

Nơi lưu file: res/layout

Nội dung file:

<layout 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">

<data>

<variable
name="car"
type="com.vantinviet.adayroionline.viewobject.Car" />
</data>

<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">

<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/md_grey_100"
android:paddingBottom="@dimen/space_16"
tools:context="com.vantinviet.adayroionline.ui.user.ProfileFragment">

<ImageView
android:id="@+id/userCoverImageView"
android:layout_width="0dp"
android:layout_height="150dp"
android:contentDescription="@string/image_default__image"
android:scaleType="centerCrop"
android:src="@drawable/default_profile"
app:imageProfileUrl="@{car.defaultPhoto.imgPath}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<ImageView
android:id="@+id/userCoverBlurImageView"
android:layout_width="0dp"
android:layout_height="150dp"
android:contentDescription="@string/image_default__image"
android:scaleType="fitXY"
android:src="@drawable/blur"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<ImageView
android:id="@+id/profileImageView"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="100dp"
android:contentDescription="@string/image_default__image"
android:src="@drawable/default_profile"
app:imageProfileUrl="@{car.defaultPhoto.imgPath}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/userCoverBlurImageView" />

<ImageView
android:id="@+id/profileEditImageView"
android:layout_width="30dp"
android:layout_height="30dp"
android:background="@drawable/rounded_corner_shape"
android:contentDescription="@string/image_default__image"
android:src="@android:drawable/ic_menu_edit"
app:layout_constraintEnd_toEndOf="@+id/profileImageView"
app:layout_constraintTop_toTopOf="@+id/profileImageView"
app:srcCompat="@android:drawable/ic_menu_edit" />


<Button
android:id="@+id/editButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:background="@drawable/rounded_corner_shape_button"
android:text="@string/edit"
android:textColor="@color/button__primary_text"
app:font='@{"normal"}'
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/userCardView"
app:textSize='@{"button_text"}'
tools:ignore="MissingConstraints"
tools:text="Edit" />

<androidx.cardview.widget.CardView
android:id="@+id/userCardView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/space_16"
android:layout_marginStart="@dimen/space_8"
android:layout_marginEnd="@dimen/space_8"
app:cardElevation="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/profileImageView">

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:id="@+id/carNameTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:text="@string/car_name"
android:textColor="@color/text__primary"
android:textSize="12sp"
app:font='@{"normal"}'
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="@string/car_name" />

<TextView
android:id="@+id/carNameEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:background="@drawable/layout_border"
android:hint="@string/car_name"
android:inputType="text"
android:minHeight="48dp"
android:paddingStart="10dp"
android:paddingTop="8dp"
android:paddingEnd="10dp"
android:paddingBottom="8dp"
android:text="@{car.carName}"
android:textColor="@color/text__primary"
android:textSize="@dimen/font_body_size"
app:font='@{"normal"}'
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/carNameTextView" />

<TextView
android:id="@+id/emailTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:text="@string/color"
android:textColor="@color/text__primary"
android:textSize="12sp"
app:font='@{"normal"}'
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/carNameEditText"
tools:text="@string/color" />

<TextView
android:id="@+id/carColorEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:background="@drawable/layout_border"
android:hint="@string/color"
android:inputType="text"
android:minHeight="48dp"
android:paddingStart="10dp"
android:paddingTop="8dp"
android:paddingEnd="10dp"
android:paddingBottom="8dp"
android:text="@{car.carColor}"
android:textColor="@color/text__primary"
android:textSize="@dimen/font_body_size"
app:font='@{"normal"}'
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/emailTextView" />

<EditText
android:id="@+id/carModelEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:background="@drawable/layout_border"
android:hint="@string/model"
android:inputType="phone"
android:minHeight="48dp"
android:paddingStart="10dp"
android:paddingTop="8dp"
android:paddingEnd="10dp"
android:paddingBottom="8dp"
android:text='@{car.carModel}'
android:textColor="@color/text__primary"
android:textSize="@dimen/font_body_size"
app:font='@{"normal"}'
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/phoneTextView" />

<TextView
android:id="@+id/carDescriptionEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:background="@drawable/layout_border"
android:hint="@string/description"
android:imeOptions="actionDone"
android:inputType="textMultiLine"
android:minHeight="48dp"
android:paddingStart="10dp"
android:paddingTop="8dp"
android:paddingEnd="10dp"
android:paddingBottom="8dp"
android:text='@{car.carDescription}'
android:textColor="@color/text__primary"
android:textSize="@dimen/font_body_size"
app:font='@{"normal"}'
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/aboutMeTextView" />

<TextView
android:id="@+id/phoneTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:text="@string/model"
android:textColor="@color/text__primary"
android:textSize="12sp"
app:font='@{"normal"}'
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/carColorEditText" />

<TextView
android:id="@+id/aboutMeTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:text="@string/description"
android:textColor="@color/text__primary"
android:textSize="12sp"
app:font='@{"normal"}'
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/carModelEditText" />

</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>


</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>

</layout>

Xây dựng class CarDetailFragment.java

Tên file: CarDetailFragment.java

Nơi lưu file: java/com/vantinviet/adayroionline/ui/car/detail (Chú ý cần tạo các sub pakage nhé)

Nội dung file:

package com.vantinviet.adayroionline.ui.car.detail;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.databinding.DataBindingUtil;
import androidx.lifecycle.ViewModelProviders;

import com.vantinviet.adayroionline.R;
import com.vantinviet.adayroionline.binding.FragmentDataBindingComponent;
import com.vantinviet.adayroionline.databinding.FragmentCarDetailBinding;
import com.vantinviet.adayroionline.ui.common.PSFragment;
import com.vantinviet.adayroionline.utils.AutoClearedValue;
import com.vantinviet.adayroionline.utils.Constants;
import com.vantinviet.adayroionline.utils.PSDialogMsg;
import com.vantinviet.adayroionline.viewmodel.car.CarViewModel;

public class CarDetailFragment extends PSFragment {

private final androidx.databinding.DataBindingComponent dataBindingComponent = new FragmentDataBindingComponent(this);
private CarViewModel carViewModel;
private String carId;
private PSDialogMsg psDialogMsg;

@VisibleForTesting
private AutoClearedValue<FragmentCarDetailBinding> binding;

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {


FragmentCarDetailBinding dataBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_car_detail, container, false, dataBindingComponent);

binding = new AutoClearedValue<>(this, dataBinding);

return binding.get().getRoot();

}

@Override
protected void initUIAndActions() {

psDialogMsg = new PSDialogMsg(getActivity(), false);
binding.get().editButton.setOnClickListener(view -> navigationController.navigateToCarFormActivity(getActivity(),carId));



}

@Override
protected void initViewModels() {

carViewModel = ViewModelProviders.of(this, viewModelFactory).get(CarViewModel.class);

}

@Override
protected void initAdapters() {

}

@Override
protected void initData() {

if (getActivity() != null) {
carId = getActivity().getIntent().getStringExtra(Constants.CAR_ID);
}

if (carId != null && !carId.isEmpty()) {
carViewModel.setCarByIdObj(carId);

carViewModel.getCarByIdData().observe(this, result -> {

if (result != null) {
if (result.data != null) {
switch (result.status) {
case SUCCESS:
binding.get().setCar(result.data);
break;

case ERROR:
psDialogMsg.showErrorDialog(getString(R.string.car_detail_error_message), getString(R.string.app__ok));
psDialogMsg.show();
break;

case LOADING:
binding.get().setCar(result.data);
break;
}
}
}
});
}

}
}

Xây dựng class CarDetailActivity.java

Tên file: CarDetailActivity.java

Nơi lưu file: java/com/vantinviet/adayroionline/ui/car/detail (Các bạn nhớ tạo các sub package nhé)

Nội dung file:

package com.vantinviet.adayroionline.ui.car.detail;

import android.os.Bundle;

import androidx.databinding.DataBindingUtil;

import com.vantinviet.adayroionline.R;
import com.vantinviet.adayroionline.databinding.ActivityCarDetailBinding;
import com.vantinviet.adayroionline.ui.common.PSAppCompactActivity;

public class CarDetailActivity extends PSAppCompactActivity {

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

ActivityCarDetailBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_car_detail);

initUI(binding);

}

private void initUI(ActivityCarDetailBinding binding) {

// Toolbar
initToolbar(binding.toolbar, getResources().getString(R.string.car_detail__title));

// setup Fragment
setupFragment(new CarDetailFragment());

}
}

Tạo layout activity_car_form.xml

Tên file: activity_car_form.xml

Nơi lưu file: res/layout

Nội dung file:

<?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">

<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/main_content"
android:layout_height="match_parent"
android:layout_width="match_parent">

<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

<androidx.appcompat.widget.Toolbar
style="@style/ToolBarStyle.Event"
android:background="@color/global__primary"
android:id="@+id/toolbar"
android:layout_height="?android:attr/actionBarSize"
android:layout_width="match_parent"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

</com.google.android.material.appbar.AppBarLayout>

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">

<FrameLayout
android:id="@+id/content_frame"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/md_white_1000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">

</FrameLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

</layout>

Tạo layout fragment_car_form.xml

Tên file: fragment_car_form.xml

Nơi lưu file: res/layout

Nội dung file:

<layout 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">

<data>

<variable
name="car"
type="com.vantinviet.adayroionline.viewobject.Car" />
</data>

<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">

<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/md_grey_100"
android:paddingBottom="@dimen/space_16"
tools:context="com.vantinviet.adayroionline.ui.user.ProfileFragment">

<ImageView
android:id="@+id/userCoverImageView"
android:layout_width="0dp"
android:layout_height="150dp"
android:contentDescription="@string/image_default__image"
android:scaleType="centerCrop"
android:src="@drawable/default_profile"
app:imageProfileUrl="@{car.defaultPhoto.imgPath}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<ImageView
android:id="@+id/userCoverBlurImageView"
android:layout_width="0dp"
android:layout_height="150dp"
android:contentDescription="@string/image_default__image"
android:scaleType="fitXY"
android:src="@drawable/blur"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<ImageView
android:id="@+id/profileImageView"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="100dp"
android:contentDescription="@string/image_default__image"
android:src="@drawable/default_profile"
app:imageProfileUrl="@{car.defaultPhoto.imgPath}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/userCoverBlurImageView" />

<ImageView
android:id="@+id/profileEditImageView"
android:layout_width="30dp"
android:layout_height="30dp"
android:background="@drawable/rounded_corner_shape"
android:contentDescription="@string/image_default__image"
android:src="@android:drawable/ic_menu_edit"
app:layout_constraintEnd_toEndOf="@+id/profileImageView"
app:layout_constraintTop_toTopOf="@+id/profileImageView"
app:srcCompat="@android:drawable/ic_menu_edit" />


<Button
android:id="@+id/saveButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:background="@drawable/rounded_corner_shape_button"
android:text="@string/edit_profile__save"
android:textColor="@color/button__primary_text"
app:font='@{"normal"}'
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/userCardView"
app:textSize='@{"button_text"}'
tools:ignore="MissingConstraints"
tools:text="Save" />

<Button
android:id="@+id/cancelButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:background="@drawable/rounded_corner_shape_button"
android:text="Cancel"
android:textColor="@color/button__primary_text"
app:font='@{"normal"}'
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/saveButton"
tools:text="Cancel" />

<androidx.cardview.widget.CardView
android:id="@+id/userCardView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/space_16"
android:layout_marginStart="@dimen/space_8"
android:layout_marginEnd="@dimen/space_8"
app:cardElevation="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/profileImageView">

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:id="@+id/carNameTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:text="@string/car_name"
android:textColor="@color/text__primary"
android:textSize="12sp"
app:font='@{"normal"}'
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="@string/car_name" />

<EditText
android:id="@+id/carNameEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:background="@drawable/layout_border"
android:hint="@string/car_name"
android:inputType="text"
android:minHeight="48dp"
android:paddingStart="10dp"
android:paddingTop="8dp"
android:paddingEnd="10dp"
android:paddingBottom="8dp"
android:text="@{car.carName}"
android:textColor="@color/text__primary"
android:textSize="@dimen/font_body_size"
app:font='@{"normal"}'
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/carNameTextView" />

<TextView
android:id="@+id/emailTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:text="@string/color"
android:textColor="@color/text__primary"
android:textSize="12sp"
app:font='@{"normal"}'
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/carNameEditText"
tools:text="@string/color" />

<EditText
android:id="@+id/carColorEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:background="@drawable/layout_border"
android:hint="@string/color"
android:inputType="text"
android:minHeight="48dp"
android:paddingStart="10dp"
android:paddingTop="8dp"
android:paddingEnd="10dp"
android:paddingBottom="8dp"
android:text="@{car.carColor}"
android:textColor="@color/text__primary"
android:textSize="@dimen/font_body_size"
app:font='@{"normal"}'
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/emailTextView" />

<EditText
android:id="@+id/carModelEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:background="@drawable/layout_border"
android:hint="@string/model"
android:inputType="phone"
android:minHeight="48dp"
android:paddingStart="10dp"
android:paddingTop="8dp"
android:paddingEnd="10dp"
android:paddingBottom="8dp"
android:text='@{car.carModel}'
android:textColor="@color/text__primary"
android:textSize="@dimen/font_body_size"
app:font='@{"normal"}'
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/phoneTextView" />

<EditText
android:id="@+id/carDescriptionEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:background="@drawable/layout_border"
android:hint="@string/description"
android:imeOptions="actionDone"
android:inputType="textMultiLine"
android:minHeight="48dp"
android:paddingStart="10dp"
android:paddingTop="8dp"
android:paddingEnd="10dp"
android:paddingBottom="8dp"
android:text='@{car.carDescription}'
android:textColor="@color/text__primary"
android:textSize="@dimen/font_body_size"
app:font='@{"normal"}'
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/aboutMeTextView" />

<TextView
android:id="@+id/phoneTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:text="@string/model"
android:textColor="@color/text__primary"
android:textSize="12sp"
app:font='@{"normal"}'
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/carColorEditText" />

<TextView
android:id="@+id/aboutMeTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:text="@string/description"
android:textColor="@color/text__primary"
android:textSize="12sp"
app:font='@{"normal"}'
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/carModelEditText" />

</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>


</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>

</layout>

Tạo class CarFormFragment.java

Tên file: CarFormFragment.java

Nơi lưu file: java/com/vantinviet/adayroionline/ui/car/form (Chu ý là bạn cần tạo các sub package nhé)

Nội dung file:

package com.vantinviet.adayroionline.ui.car.form;

import android.app.ProgressDialog;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.databinding.DataBindingUtil;
import androidx.lifecycle.ViewModelProviders;

import com.vantinviet.adayroionline.R;
import com.vantinviet.adayroionline.binding.FragmentDataBindingComponent;
import com.vantinviet.adayroionline.databinding.FragmentCarFormBinding;
import com.vantinviet.adayroionline.ui.common.PSFragment;
import com.vantinviet.adayroionline.utils.AutoClearedValue;
import com.vantinviet.adayroionline.utils.Constants;
import com.vantinviet.adayroionline.utils.PSDialogMsg;
import com.vantinviet.adayroionline.utils.Utils;
import com.vantinviet.adayroionline.viewmodel.car.CarViewModel;
import com.vantinviet.adayroionline.viewobject.Car;
import com.vantinviet.adayroionline.viewobject.User;

public class CarFormFragment extends PSFragment {

private final androidx.databinding.DataBindingComponent dataBindingComponent = new FragmentDataBindingComponent(this);
private CarViewModel carViewModel;
private String carId;
private PSDialogMsg psDialogMsg;
private AutoClearedValue<ProgressDialog> prgDialog;
@VisibleForTesting
private AutoClearedValue<FragmentCarFormBinding> binding;

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {


FragmentCarFormBinding dataBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_car_form, container, false, dataBindingComponent);

binding = new AutoClearedValue<>(this, dataBinding);

return binding.get().getRoot();

}

@Override
protected void initUIAndActions() {

psDialogMsg = new PSDialogMsg(getActivity(), false);
binding.get().saveButton.setOnClickListener(view -> CarFormFragment.this.editCarData());
binding.get().cancelButton.setOnClickListener(view -> {
navigationController.navigateToCarList(getActivity());
});
// Init Dialog
prgDialog = new AutoClearedValue<>(this, new ProgressDialog(getActivity()));

prgDialog.get().setMessage((Utils.getSpannableString(getContext(), getString(R.string.message__please_wait), Utils.Fonts.MM_FONT)));
prgDialog.get().setCancelable(false);


}
private void editCarData() {

if (!connectivity.isConnected()) {

psDialogMsg.showWarningDialog(getString(R.string.no_internet_error), getString(R.string.app__ok));
psDialogMsg.show();
return;
}

String carName = binding.get().carNameEditText.getText().toString();
if (carName.equals("")) {

psDialogMsg.showWarningDialog(getString(R.string.error_message__blank_name), getString(R.string.app__ok));
psDialogMsg.show();
return;
}

String carColor = binding.get().carColorEditText.getText().toString();
if (carColor.equals("")) {

psDialogMsg.showWarningDialog(getString(R.string.error_message__blank_color), getString(R.string.app__ok));
psDialogMsg.show();
return;
}
String carModel = binding.get().carModelEditText.getText().toString();
if (carModel.equals("")) {

psDialogMsg.showWarningDialog(getString(R.string.error_message__blank_model), getString(R.string.app__ok));
psDialogMsg.show();
return;
}

updateCarData();
carViewModel.getUpdateCarData().observe(this, apiResponseGetCar -> {

if (apiResponseGetCar != null) {

Utils.psLog("Got Data" + apiResponseGetCar.message + apiResponseGetCar.toString());

switch (apiResponseGetCar.status) {
case LOADING:
// Loading State
// Data are from Local DB
// if(listResource.data != null){
// fadeIn(binding.get().getRoot());
// }

break;
case SUCCESS:
// Success State
// Data are from Server

if (apiResponseGetCar.data != null) {

// userViewModel.updateUser(userViewModel.user);

psDialogMsg.showSuccessDialog(apiResponseGetCar.data.errorMessage, getString(R.string.app__ok));
psDialogMsg.show();
psDialogMsg.okButton.setOnClickListener(view -> {
psDialogMsg.cancel();
navigationController.navigateToCarList(getActivity());
});


}
carViewModel.setLoadingState(false);
prgDialog.get().cancel();


break;
case ERROR:
// Error State

psDialogMsg.showErrorDialog(apiResponseGetCar.message, getString(R.string.app__ok));
psDialogMsg.show();
prgDialog.get().cancel();

carViewModel.setLoadingState(false);
break;
default:


break;
}

} else {

// Init Object or Empty Data
Utils.psLog("Empty Data");
}


});
}
private void updateCarData() {
Car car = new Car(
carId,
binding.get().carNameEditText.getText().toString(),
binding.get().carModelEditText.getText().toString(),
binding.get().carColorEditText.getText().toString(),
binding.get().carDescriptionEditText.getText().toString(),
""
);
carViewModel.setUpdateCarObj(car);

prgDialog.get().show();
}


private boolean checkToUpdateCar() {
return binding.get().carNameTextView.getText().toString().equals(carViewModel.car.carName) &&
binding.get().carModelEditText.getText().toString().equals(carViewModel.car.carModel) &&
binding.get().carColorEditText.getText().toString().equals(carViewModel.car.carColor);
}

@Override
protected void initViewModels() {

carViewModel = ViewModelProviders.of(this, viewModelFactory).get(CarViewModel.class);

}

@Override
protected void initAdapters() {

}

@Override
protected void initData() {

if (getActivity() != null) {
carId = getActivity().getIntent().getStringExtra(Constants.CAR_ID);
}

if (carId != null && !carId.isEmpty()) {
carViewModel.setCarByIdObj(carId);

carViewModel.getCarByIdData().observe(this, result -> {

if (result != null) {
if (result.data != null) {
switch (result.status) {
case SUCCESS:
binding.get().setCar(result.data);
break;

case ERROR:
psDialogMsg.showErrorDialog(getString(R.string.car_detail_error_message), getString(R.string.app__ok));
psDialogMsg.show();
break;

case LOADING:
binding.get().setCar(result.data);
break;
}
}
}
});
}

}
}

Tạo class CarFormActivity.java

Tên file: CarFormActivity.java

Nơi lưu file: java/com/vantinviet/adayroionline/ui/car/form

Nội dung file:

package com.vantinviet.adayroionline.ui.car.form;

import android.os.Bundle;

import androidx.databinding.DataBindingUtil;

import com.vantinviet.adayroionline.R;
import com.vantinviet.adayroionline.databinding.ActivityBlogDetailBinding;
import com.vantinviet.adayroionline.databinding.ActivityCarFormBinding;
import com.vantinviet.adayroionline.ui.car.detail.CarDetailFragment;
import com.vantinviet.adayroionline.ui.common.PSAppCompactActivity;

public class CarFormActivity extends PSAppCompactActivity {

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

ActivityCarFormBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_car_form);

initUI(binding);

}

private void initUI(ActivityCarFormBinding binding) {

// Toolbar
initToolbar(binding.toolbar, getResources().getString(R.string.car_form_title));

// setup Fragment
setupFragment(new CarFormFragment());

}
}

Cập nhật layout trong AndroidManifest.xml

Mở file AndroidManifest.xml

Bổ dung các thẻ sau vào thẻ   application

 

<activity android:name="com.vantinviet.adayroionline.ui.car.list.CarListActivity"
android:exported="true"
android:theme="@style/Base.PSTheme" />
<activity android:name="com.vantinviet.adayroionline.ui.car.detail.CarDetailActivity"
android:exported="true"
android:theme="@style/Base.PSTheme" />
<activity android:name="com.vantinviet.adayroionline.ui.car.form.CarFormActivity"
android:exported="true"
android:theme="@style/Base.PSTheme" />

Bổ xung thông tin vào MainActivityModule

Mở file java/com/vantinviet/adayroionline/di/MainActivityModule.java

Tìm  đến  abstract class MainActivityModule 

Bổ xung dòng sau xuống cuối

 

@ContributesAndroidInjector(modules = CarActivityModule.class)
abstract CarListActivity carListActivity();
@ContributesAndroidInjector(modules = CarDetailActivityModule.class)
abstract CarDetailActivity carDetailActivity();
@ContributesAndroidInjector(modules = CarFormActivityModule.class)
abstract CarFormActivity carFormActivity();


bổ xung nhưng dòng code sau xuống cuối file


@Module
abstract class CarActivityModule {
@ContributesAndroidInjector
abstract CarListFragment contributeCarListFragment();
}
@Module
abstract class CarDetailActivityModule {
@ContributesAndroidInjector
abstract CarDetailFragment contributeCarDetailFragment();
}
@Module
abstract class CarFormActivityModule {
@ContributesAndroidInjector
abstract CarFormFragment contributeCarFormFragment();
}


Hoàn thành

Trên đây là toàn bộ các bước tạo một view quản lý car bao gồm hiển thị danh sách, thêm sửa xóa,

hiện tác bạn thao tác máy móc như vậy mà chạy là đã ok rồi, bước tiếp theo các bạn cần phải đọc code lại, để hiểu sau hơn, ngoài ra trong quá trình làm các bạn cũng nên đọc lại code để sau này có thể hiểu bản chất của nhưng việc tạo này

Cám ơn bạn đã đọc tài liệu của chúng tôi

Công ty cổ phần thương mại Vạn Tín Việt

0936.006.058
0936.006.058