整个示例项目,两个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. 【BUS】动画图解嵌入式常见的通讯协议:SPI、I²C、UART、红外 ......

    SPI传输 SPI数据传输 SPI数据传输 SPI时序信号 I2C传输 2C总线寻址 UART传输 PC-UART-MCU RS-232电平转换 红外控制 红外通信 红外信号接收.放大.整形 红外控制 ...

  2. 改变vs私有变量的命名规范

    vs默认情况下,private 变量是不带下划线开头的,可以通过设置命名规范,增加下划线开头规则. 点击菜单:[工具]->[选项]->[文本编辑器]->[c#]->[代码样式] ...

  3. [转帖]为什么 Java 内部使用 UTF-16 表示字符串?

    https://www.jianshu.com/p/957b249a02d8 背景 许多年前 Unicode 的提出者天真地以为 16 位定长的字符可以容纳地球上所有仍具活力的文字,Java 设计者也 ...

  4. [转帖]oceanbase 的简单介绍

    English | 中文版 OceanBase Database 是一个分布式关系型数据库.完全由蚂蚁集团自主研发. OceanBase 基于 Paxos 协议以及分布式架构,实现了高可用和线性扩展. ...

  5. nexus的简单安装与使用

    nexus的简单安装与使用 文件下载 官网上面下载文件比较麻烦, 得科学一些 https://www.sonatype.com/download-oss-sonatype 选择oss 开源版进行下载 ...

  6. [转帖]一文解决内核是如何给容器中的进程分配CPU资源的?

    https://zhuanlan.zhihu.com/p/615570804   现在很多公司的服务都是跑在容器下,我来问几个容器 CPU 相关的问题,看大家对天天在用的技术是否熟悉. 容器中的核是真 ...

  7. 【转帖】用pycharm开发django项目示例

    https://www.cnblogs.com/kylinlin/p/5184592.html pycharm开发django工程(一) 在pycharm(企业版)中新建Django工程,注意使用虚拟 ...

  8. [转帖]解读内核 sysctl 配置中 panic、oops 相关项目

    写在前面 本篇文章的内容主要来自内核源码树 Documentation/admin-guide/sysctl/kernel.rst文件. softlockup vs hardlockup softlo ...

  9. Sysbench简单测试数据库性能

    摘要 先进行了一个PG数据库的测试. Mysql数据库的测试稍后跟上. 紧接着上一篇的安装, 部分文件可能需要特定路径才可以. sysbench 测试的说明 一个参数 这里稍微说一下参数的问题 sys ...

  10. vim 复制代码的方法

    之前vim 复制代码 总是格式变错乱了 尤其是yaml文件 有的还带注释 非常痛苦 今天早上查了下 原来处理的方式非常简单  增加一个参数就可以了 方法为 1. vim 打开一个文件 2.输入 :se ...