整个示例项目,两个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");     }
};

  

BasicSample项目说明的更多相关文章

  1. Android开发学习总结(三)——appcompat_v7项目说明

    一.appcompat_v7项目说明 今天来说一下appcompat_v7项目的问题,使用eclipse创建Android项目时,发现project列表中会多创建出一个appcompat_v7项目,这 ...

  2. VUE (vue-cli)脚手架项目说明

    1. 概述 1.1 说明 使用vue-cli快速创建的vue项目目录如下: build  -- webpack相关配置以及服务启动文件,配置多依赖于下边的config文件夹中内容 config -- ...

  3. 基于 Vue+Mint-ui 的 Mobile-h5 的项目说明

    Vue作为前端三大框架之一,其已经悄然成为主流,学会用vue相关技术来开发项目会相当轻松. 对于还没学习或者还没用过vue的初学者,基础知识这里不作详解,推荐先去相关官网,学习一下vue相关的基础知识 ...

  4. Django---图书管理系统,一对多(外键设置),__str__和__repr__的区别,进阶版项目说明简介.模版语言if ... else ..endif

    Django---图书管理系统,一对多(外键设置),__str__和__repr__的区别,进阶版项目说明简介.模版语言if ... else ..endif 一丶__str__ 和 __repr__ ...

  5. day01-家具网购项目说明

    家具网购项目说明 1.项目前置技术 Java基础 正则表达式 Mysql JDBC 数据库连接池技术 满汉楼项目(包括框架图) JavaWeb 2.相关说明 这里先使用原生的servlet/过滤器,后 ...

  6. TchApp项目说明

    概述 使用Web做UI,csharp编程,构建跨平台桌面软件,项目目标是打造一个框架,做完相关的基础设施,让使用者能够只关注业务开发,而不需要重新构建基础设施. 项目应该包括:窗口管理API,跨语言( ...

  7. Android开发之搜Ya项目说明(3)

    项目 搜芽移动client ----seller,app,base三个包的简单说明 作者 曾金龙 Tel:18664312687 QQ :470910357@qq.com 时间 2014-10-14 ...

  8. 从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 二十║Vue基础终篇:传值+组件+项目说明

    缘起 新的一天又开始啦,大家也应该看到我的标题了,是滴,Vue基础基本就到这里了,咱们回头看看这一路,如果你都看了,并且都会写了,那么现在你就可以自己写一个Demo了,如果再了解一点路由,ajax请求 ...

  9. net core体系-web应用程序-4asp.net core2.0 项目实战(任务管理系统)-1项目说明

    https://www.bug2048.com/netcore20180313/ 最近公司的一个小项目尝试使用 .net core作为服务端进行开发,并顺利上线运行了一段时间,整体效果还是比较满意的. ...

  10. net core体系-web应用程序-4asp.net core2.0 项目实战(1)-2项目说明和源码下载

    本文目录1. 摘要2. Window下运行 3.linux下运行4. 开发记录5. 总结 1.概要 写<Asp.Net Core 2.0 项目实战>系列断断续续已经很长时间了,期间很多朋友 ...

随机推荐

  1. Python毕业设计推荐

    今天给大家推荐几个基于python/django的毕业设计/课程设计. 1. 网上商城系统 这是一个基于python+vue开发的商城网站,平台采用B/S结构,后端采用主流的Python语言进行开发, ...

  2. springboot - 解决使用pagehelper 报 SQL语句异常

    原因: mapper.xml 中的sql加上了分号. <select id="search" resultType="***.Table"> sel ...

  3. SpringBoot3集成Zookeeper

    标签:Zookeeper3.8 ,Curator5.5: 一.简介 ZooKeeper是一个集中的服务,用于维护配置信息.命名.提供分布式同步.提供组服务.分布式应用程序以某种形式使用所有这些类型的服 ...

  4. C++开发PHP扩展

    前端时间用C开发PHP扩展,用C实现字符串和简单的cache不友好,因而有了用C++开发的想法. 相关环境初始化配置准备 1.用php源码提供的脚手架生成扩展名 php ext/ext_skel.ph ...

  5. 【面试题精讲】Redis如何实现分布式锁

    首发博客地址 系列文章地址 Redis 可以使用分布式锁来实现多个进程或多个线程之间的并发控制,以确保在给定时间内只有一个进程或线程可以访问临界资源.以下是一种使用 Redis 实现分布式锁的常见方法 ...

  6. Python 变量?对象?引用?赋值?一个例子解释清楚

    哈喽大家好,我是咸鱼. 前天有个小伙伴找到我,给了我一段 python 代码: a = [1, 2] a[1] = a print(a[1]) 然后问我为什么结果是 [1, [...]],我一看这个问 ...

  7. [转帖]SQL Server高级进阶之索引碎片维护

    https://www.cnblogs.com/atomy/p/15268589.html 一.产生原因及影响 索引是数据库引擎中针对表(有时候也针对视图)建立的特别数据结构,用来帮助查找和整理数据, ...

  8. [转帖]awk命令 去掉重复行

    https://developer.aliyun.com/article/885946?spm=a2c6h.24874632.expert-profile.263.7c46cfe9h5DxWK lin ...

  9. [1036]Linux启动时间分析

    简述 今天有同事咨询:项目上有台服务器操作系统启动时间较长,如何分析? 果然,好问题都来自实践. 经过查找,对于所有基于systemd的系统,可以使用systemd-analyze来分析系统启动时间. ...

  10. 六个节点三主三从Redis集群最简单搭建方法

    背景 一个服务器上面的三主三从的界面太low,容易出问题. 为了验证高可用, 我这边使用六台机器进行了三主三从的搭建. 模仿开源版的一键搭建集群的脚本进行使用,感觉非常简单,这里简单进行一下总结. 环 ...