记录数据使用ROOM,传递使用ViewModel LiveDataBus,这篇文章主要记录 搜索记录 本地 线上,上传失败,记录本地,网络回复统一上传等逻辑的操作。
目录
首先是设计数据表:
定义DAO操作接口
定义数据库类
Mvvm 模式中封装使用Room
首先阐述下搜索记录的要求,后面会逐一针对这些要求使用Room本身能力解决。
- 最多记录10条;
- 搜索分为点位搜索,和普通搜索。
- 列表展示输入搜索的文字,要求文字不可有重复。
- 记录分为本地记录和在线记录。
首先是设计数据表:
@Entity(indices = {@Index(value = {"searchKeyword", "searchType"}, unique = true)})
public class SearchHistoryEntity {@PrimaryKey(autoGenerate = true)public int id;public String userId; // 可以为null表示未登录用户public String searchKeyword;public long searchTime;@TypeConverters(Converters.class)public SearchType searchType; // 使用枚举类型public double latitude; // 纬度public double longitude; // 经度public boolean loginStatus; // false: 未登录, true: 已登录}
@Entity
表示这是一个 Room 数据库中的表。indices
参数定义了一个组合索引searchKeyword
和searchType
,并且设置为唯一索引。这意味着在表中,searchKeyword
和searchType
的组合必须是唯一的。
serchType 是枚举类型,为了区分 普通搜索和点位搜索。
设置searchKeyword 和 searchType 组合必须是唯一的。
以上可以满足第二、三条要求。
定义DAO操作接口
@Dao
public interface SearchHistoryDao {// 插入新的搜索记录,如果发生冲突则替换@Insert(onConflict = OnConflictStrategy.REPLACE)long insertPerson(SearchHistoryEntity entity);@Query("DELETE FROM SearchHistoryEntity WHERE id = (SELECT id FROM SearchHistoryEntity ORDER BY searchTime ASC LIMIT 1)")void deleteOldestSearchHistory();// 删除所有的搜索历史记录@Query("DELETE FROM SearchHistoryEntity")void deleteAllSearchHistory();// 查询所有历史记录@Query("SELECT * FROM SearchHistoryEntity ORDER BY searchTime DESC")public List<SearchHistoryEntity> selectHis();// 查询所有已登录的数据@Query("SELECT * FROM SearchHistoryEntity WHERE userId = :userId")List<SearchHistoryEntity> getSearchHistoryByUserId(String userId);// 查询所有未登录的数据@Query("SELECT * FROM SearchHistoryEntity WHERE loginStatus = 0 ORDER BY searchTime DESC")List<SearchHistoryEntity> getAllLoggedOutData();// 删除所有已登录的数据@Query("DELETE FROM SearchHistoryEntity WHERE loginStatus = 1")void deleteAllLoggedInData();}
这个类其实就是操作数据的工具类,里面最主要需要解释的只有
@Insert(onConflict = OnConflictStrategy.REPLACE)
: 该注解表明该方法用于向数据库插入数据。onConflict
参数设置为OnConflictStrategy.REPLACE
,这意味着如果发生冲突(例如已经存在具有相同主键的行),则用新数据替换现有行。
这样是第二天 第三条的补充,在插入相同搜索的记录的时候覆盖原有记录。
定义数据库类
@Database(entities = {SearchHistoryEntity.class}, version = 1, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {public abstract SearchHistoryDao searchHistoryDao();private static volatile AppDatabase INSTANCE;private static final int NUMBER_OF_THREADS = 4;static final ExecutorService databaseWriteExecutor = Executors.newFixedThreadPool(NUMBER_OF_THREADS);static AppDatabase getDatabase(final Context context) {if (INSTANCE == null) {synchronized (AppDatabase.class) {if (INSTANCE == null) {INSTANCE = Room.databaseBuilder(context.getApplicationContext(),AppDatabase.class, "app_database").addCallback(sRoomDatabaseCallback).build();}}}return INSTANCE;}private static RoomDatabase.Callback sRoomDatabaseCallback =new RoomDatabase.Callback() {@Overridepublic void onCreate(@NonNull SupportSQLiteDatabase db) {super.onCreate(db);// 创建触发器:确保最多保留10条记录db.execSQL("CREATE TRIGGER limit_search_history_count " +"AFTER INSERT ON SearchHistoryEntity " +"WHEN (SELECT COUNT(*) FROM SearchHistoryEntity) > 10 " +"BEGIN " +"DELETE FROM SearchHistoryEntity WHERE id = (SELECT id FROM SearchHistoryEntity ORDER BY searchTime ASC LIMIT 1); " +"END");}};
}
这里是一个模版代码,其中唯一注意的sRoomDatabaseCallback ,添加一个触发器,当数据超过10条后,按照添加顺序删除最早添加的数据。
其实和DAO接口中定义的 deleteOldestSearchHistory 方法功能是一样的,但是显然触发器的方式更省心。不需要我们每次添加都要去获取一次存储了多少条数据。再去删除。
满足第一条
以上就是Room完整的模版代码,下面才是最主要的第四个条件。
我项目框架是Mvvm,所以操作数据库的代码就放到了 Repository 中,其实没必要,但是咱们尊许模式搞一遍,不在意的话直接使用 db = AppDatabase.getDatabase(this); 即可
Mvvm 模式中封装使用Room
- 创建 AppDataBase 抽象类
@TypeConverters(value = {Converters.class})
@Database(entities = {SearchHistoryEntity.class}, version = 1)
public abstract class AppDataBase extends RoomDatabase {public abstract SearchHistoryDao searchHistoryDao();}
对了,差点忘记 Converters 了,在数据库中使用枚举 JSONObject 需要为其创建一个转换器
public class Converters {@TypeConverterpublic static SearchType toSearchType(String value) {return value == null ? null : SearchType.valueOf(value);}@TypeConverterpublic static String fromSearchType(SearchType searchType) {return searchType == null ? null : searchType.name();}
}
- 创建 单利 DbUtil 帮助类
public class DbUtil {private AppDataBase appDataBase;private static DbUtil instance;private Context context;private String dbName;public static DbUtil getInstance() {if (instance == null) {instance = new DbUtil();}return instance;}public void init(Context context,String dbName) {this.context =context.getApplicationContext();this.dbName = dbName;appDataBase = null;}public AppDataBase getAppDataBase() {if (appDataBase == null) {if (TextUtils.isEmpty(dbName)) {throw new NullPointerException("dbName is null");}appDataBase = Room.databaseBuilder(context, AppDataBase.class, dbName).allowMainThreadQueries().enableMultiInstanceInvalidation()
// .addMigrations(MIGRATION_1_2).addCallback(sRoomDatabaseCallback).build();}return appDataBase;}/*** 数据库版本 1->2 user表格新增了age列*/static final Migration MIGRATION_1_2 = new Migration(1, 2) {@Overridepublic void migrate(SupportSQLiteDatabase database) {}};/*** 数据库版本 2->3 新增book表格*/static final Migration MIGRATION_2_3 = new Migration(2, 3) {@Overridepublic void migrate(SupportSQLiteDatabase database) {}};private static RoomDatabase.Callback sRoomDatabaseCallback =new RoomDatabase.Callback() {@Overridepublic void onCreate(@NonNull SupportSQLiteDatabase db) {super.onCreate(db);db.execSQL("CREATE TRIGGER limit_search_history_count " +"AFTER INSERT ON SearchHistoryEntity " +"WHEN (SELECT COUNT(*) FROM SearchHistoryEntity) > 30 " +"BEGIN " +"DELETE FROM SearchHistoryEntity WHERE id = (SELECT id FROM SearchHistoryEntity ORDER BY searchTime ASC LIMIT 1); " +"END");}};}
MIGRATION_1_2 就是数据升级的操作,这里不做介绍。
- 创建 Repository
public class HomeSearchRepository extends BaseModel {private SearchHistoryDao searchHistoryDao;public HomeSearchRepository() {searchHistoryDao = DbUtil.getInstance().getAppDataBase().searchHistoryDao();}/*** Description:插入查询历史(name 相同,忽略策略)* author:clp* ModificationTime: 2024/12/26 13:06*/public long insertHis(SearchHistoryEntity entity) {return searchHistoryDao.insertPerson(entity);}
}
- 创建 ViewModel
ublic class HomeSearchViewModel extends BaseViewModel<HomeSearchRepository> {public MutableLiveData<List<SearchHistoryEntity>> searchHistories = new MutableLiveData<>();public HomeSearchViewModel(@NonNull Application application) {super(application);initAroundSearchDatas();gson = new Gson();}/*** 保存搜索历史** @param entity 搜索的内容*/public void insertHistory(SearchHistoryEntity entity) {if (isLogin) {uploadHistory(entity,true);} else {if (model.insertHis(entity) != -1) {searchHistories.postValue(model.getAllLoggedOutData());}}}
}
isLogin 判断是否登录,
登录状态 走接口上传,未登录状态 直接存入数据库,存入成功 postValue 到Activity 接收。
Activity代码就不贴出来了。也就是
ViewModel.observe(this, new Observer<List<SearchHistoryEntity>>() {@Overridepublic void onChanged(List<SearchHistoryEntity> searchHistoryEntities) {if (searchHistoryEntities != null) {mSearchHistoryAdapter.setNewData(searchHistoryEntities);}} });
如果对Mvvm 框架使用感兴趣可以参考我上篇文章 Mvvm + viewModel