BasicSample項目說明
2023-07-30 11:31 ttylinux 閱讀(50) 評論(0) 收藏 舉報整個示例項目,兩個Fragment,ProductListFragment和ProductFragment,一個MainActivity。在MainActivity里面展示的是ProductListFragment,點擊每個Item,
會進入相應的ProductFragment。
相關技術點說明:
LiveData應用:
LiveData的應用,當LiveData包裝的數據發生變化的時候,更新List
private void subscribeUi(LiveData<List<ProductEntity>> liveData) {
// Update the list when the data changes
liveData.observe(getViewLifecycleOwner(), myProducts -> {
if (myProducts != null) {
mBinding.setIsLoading(false);
mProductAdapter.setProductList(myProducts);
} else {
mBinding.setIsLoading(true);
}
// espresso does not know how to wait for data binding's loop so we execute changes
// sync.
mBinding.executePendingBindings();
});
}
保存LiveData的狀態數據,并且根據狀態數據的變化,更新LiveData:
Transformations.switchMap和SavedStateHandle的應用
public ProductListViewModel(@NonNull Application application,
@NonNull SavedStateHandle savedStateHandle) {
super(application);
mSavedStateHandler = savedStateHandle;
mRepository = ((BasicApp) application).getRepository();
// Use the savedStateHandle.getLiveData() as the input to switchMap,
// allowing us to recalculate what LiveData to get from the DataRepository
// based on what query the user has entered
//LiveData<String> query,數據發生變化,就會觸發執行向數據庫查詢的動作,將查詢結果添加到已有的LiveData中
mProducts = Transformations.switchMap(
savedStateHandle.getLiveData("QUERY", null),
(Function<CharSequence, LiveData<List<ProductEntity>>>) query -> {
if (TextUtils.isEmpty(query)) {
return mRepository.getProducts();
}
return mRepository.searchProducts("*" + query + "*");
});
}
public void setQuery(CharSequence query) {
// Save the user's query into the SavedStateHandle.
// This ensures that we retain the value across process death
// and is used as the input into the Transformations.switchMap above
//保存在SavedStateHandler的數據,即使進程銷毀重建都會存在
mSavedStateHandler.set(QUERY_KEY, query);
}
實際的數據獲取實現,封裝在DataRepository mRepository;中。
RecyclerView - DiffUtil 局部刷新
輸入兩個數據集合,計算兩個數據集合的差異化,然后根據差異化結果更新列表。差異化的計算過程是在UI線程中進行的。
如果數據量巨大,可以在異步線程更新,使用AsyncListDiffer和ListAdapter。
public void setProductList(final List<? extends Product> productList) {
if (mProductList == null) {
mProductList = productList;
notifyItemRangeInserted(0, productList.size());
} else {
DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {
@Override
public int getOldListSize() {
return mProductList.size();
}
@Override
public int getNewListSize() {
return productList.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return mProductList.get(oldItemPosition).getId() ==
productList.get(newItemPosition).getId();
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
Product newProduct = productList.get(newItemPosition);
Product oldProduct = mProductList.get(oldItemPosition);
return newProduct.getId() == oldProduct.getId()
&& TextUtils.equals(newProduct.getDescription(), oldProduct.getDescription())
&& TextUtils.equals(newProduct.getName(), oldProduct.getName())
&& newProduct.getPrice() == oldProduct.getPrice();
}
});
mProductList = productList;
result.dispatchUpdatesTo(this);
}
}
回調函數方法的含義
public abstract static class Callback {
/**
* 舊數據 size
*/
public abstract int getOldListSize();
/**
* 新數據 size
*/
public abstract int getNewListSize();
/**
* DiffUtil 調用判斷兩個 itemview 對應的數據對象是否一樣. 由于 DiffUtil 是對兩個不同數據集合的對比, 所以比較對象引用肯定是不行的, 一般會使用 id 等具有唯一性的字段進行比較.
* @param oldItemPosition 舊數據集合中的下標
* @param newItemPosition 新數據集合中的下標
* @return True 返回 true 即判斷兩個對象相等, 反之則是不同的兩個對象.
*/
public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);
/**
* Diffutil 調用判斷兩個相同對象之間的數據是否不同. 此方法僅會在 areItemsTheSame() 返回 true 的情況下被調用.
*
* @param oldItemPosition 舊數據集合中的下標
* @param newItemPosition 新數據集合中用以替換舊數據集合數據項的下標
* @return True 返回 true 代表 兩個對象的數據相同, 反之則有差別.
*/
public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition);
/**
* 當 areItemsTheSame() 返回true areContentsTheSame() 返回 false 時, 此方法將被調用, 來完成局部刷新功能.
*/
@Nullable
public Object getChangePayload(int oldItemPosition, int newItemPosition);
}
DataBinding,數據綁定
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable name="product"
type="com.example.android.persistence.model.Product"/>
<variable name="callback"
type="com.example.android.persistence.ui.ProductClickCallback"/>
</data>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/product_item_min_height"
android:onClick="@{() -> callback.onClick(product)}"
android:orientation="horizontal"
android:layout_marginStart="@dimen/item_horizontal_margin"
android:layout_marginEnd="@dimen/item_horizontal_margin"
app:cardUseCompatPadding="true">
<RelativeLayout
android:layout_marginStart="@dimen/item_horizontal_margin"
android:layout_marginEnd="@dimen/item_horizontal_margin"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/cd_product_name"
android:text="@{product.name}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_marginEnd="5dp"
android:text="@{@string/product_price(product.price)}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/name"
android:text="@{product.description}"/>
</RelativeLayout>
</androidx.cardview.widget.CardView>
</layout>
實例化Binding,為Binding注入相應的對象
@Override
@NonNull
public ProductViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ProductItemBinding binding = DataBindingUtil
.inflate(LayoutInflater.from(parent.getContext()), R.layout.product_item,
parent, false);
binding.setCallback(mProductClickCallback);
return new ProductViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull ProductViewHolder holder, int position) {
holder.binding.setProduct(mProductList.get(position));
//立即刷新數據
holder.binding.executePendingBindings();
}
=================================================================================================================================
數據層,創建三個數據表,ProductFtsEntity,ProductEntity,CommentEntity。然后聲明Dao,通過Dao來獲取數據庫中的數據,與數據庫交互。
Dao:
@Dao
public interface CommentDao {
@Query("SELECT * FROM comments where productId = :productId")
LiveData<List<CommentEntity>> loadComments(int productId);
@Query("SELECT * FROM comments where productId = :productId")
List<CommentEntity> loadCommentsSync(int productId);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertAll(List<CommentEntity> comments);
}
應用@TypeConverter,在聲明Entity的時候,使用屬性類型不是基本類型時,是復雜的數據類型時,比如是Date類型時,需要
定義TypeConverter,這樣數據庫才知道如何存儲該數據。
定義TypeConverter需要實現兩個方法,一個是從基礎數據獲取復雜數據的方法,一個是復雜數據轉換為基礎數據的方法,比如對于類型Date:
public class Converters {
@TypeConverter
public static Date fromTimestamp(Long value) {
return value == null ? null : new Date(value);
}
@TypeConverter
public static Long dateToTimestamp(Date date) {
return date == null ? null : date.getTime();
}
}
數據庫實例,設計為一個單例,使用單例模式:
單例模式,設計為線程安全的,首先檢測是否為null,二次檢測,加鎖,判斷是否為null,如果為null,
則在當前線程創建數據庫實例。
public static AppDatabase getInstance(final Context context, final AppExecutors executors) {
if (sInstance == null) {
synchronized (AppDatabase.class) {
if (sInstance == null) {
sInstance = buildDatabase(context.getApplicationContext(), executors);
sInstance.updateDatabaseCreated(context.getApplicationContext());
}
}
}
return sInstance;
}
通過Room.databaseBuilder,builder模式構建數據庫實例。
在構建的時候,添加回調方法,首次創建的時候,數據庫創建成功,則在第三方線程為數據庫插入數據,也就是在onCreate方法里面執行。
添加數據庫升級策略。
/**
* Build the database. {@link Builder#build()} only sets up the database configuration and
* creates a new instance of the database.
* The SQLite database is only created when it's accessed for the first time.
*/
private static AppDatabase buildDatabase(final Context appContext,
final AppExecutors executors) {
return Room.databaseBuilder(appContext, AppDatabase.class, DATABASE_NAME)
.addCallback(new Callback() {
@Override
public void onCreate(@NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
executors.diskIO().execute(() -> {
// Add a delay to simulate a long-running operation
addDelay();
// Generate the data for pre-population
AppDatabase database = AppDatabase.getInstance(appContext, executors);
List<ProductEntity> products = DataGenerator.generateProducts();
List<CommentEntity> comments =
DataGenerator.generateCommentsForProducts(products);
insertData(database, products, comments);
// notify that the database was created and it's ready to be used
database.setDatabaseCreated();
});
}
})
.addMigrations(MIGRATION_1_2)
.build();
}
數據庫升級策略,添加Migrations,如下,從版本1升級到版本2,執行如下SQL語句:
如果不存在productFts數據庫表,則創建它;并且從products表查詢數據來填充productFts數據表。
private static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("CREATE VIRTUAL TABLE IF NOT EXISTS `productsFts` USING FTS4("
+ "`name` TEXT, `description` TEXT, content=`products`)");
database.execSQL("INSERT INTO productsFts (`rowid`, `name`, `description`) "
+ "SELECT `id`, `name`, `description` FROM products");
}
};
版權聲明:
作者:ttylinux
本文版權歸作者,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
浙公網安備 33010602011771號