Android Jetpack学习系列——Room

关于Room:

Room是Android Jetpack组件之一,旨在为Android应用程序提供一种简单、强大且易于使用的本地数据库访问解决方案。

关键特性:

1.基于SQLite封装:Room是基于SQLite数据库引擎构建的,提供了面向对象的API来与SQLite交互,使得开发者无需直接编写SQL语句,降低了SQL错误风险,提高了代码可读性和可维护性。

2.编译时验证:Room利用注解处理器在编译时检查查询语句的有效性,提前发现潜在的SQL语法错误或类型不匹配等问题,增强了开发过程中的错误检查能力。

3.类型安全:Room通过提供数据访问对象(DAO)接口和数据模型类,确保了数据库操作与Java/Kotlin对象之间的类型安全转换,避免了运行时的类型转换异常。

4.LiveData / Flow 支持:Room可以轻松配合LiveData或Kotlin Flow实现数据变化的实时观察与响应,便于在UI层自动更新数据,适用于MVVM架构中的数据绑定。

5.事务管理:Room提供了便捷的事务管理机制,可以通过@Transaction注解在DAO方法上标记,确保一组数据库操作的原子性。

6.查询重用与优化:Room支持定义可重用的查询方法,同时也支持查询缓存以提高性能。

使用要点:

1.定义数据模型类:使用注解(如@Entity、@PrimaryKey、@ColumnInfo等)定义数据库表对应的Java或Kotlin类。

2.创建DAO接口:使用@Dao注解创建数据访问对象接口,定义查询、插入、更新、删除等数据库操作方法。

3.创建RoomDatabase子类:定义一个继承自RoomDatabase的抽象类,声明包含的实体类与DAO。

4.初始化数据库:在应用启动时创建并持有RoomDatabase实例,通常使用单例模式。

5.执行数据库操作:通过获取的DAO实例,调用其方法进行数据库操作。

好了,至此,前面的文案部分摘抄完毕,相信大家也从不少地方看到过很多理论知识,但是实践起来总不是那么的理想化,有各种各样的问题,对吧。

先来看本文实现的效果:


 

可以看到,demo实现的是非常基础的增删改查功能

下面开始具体的实现

本文使用的开发环境:

         Android Studio Iguana | 2023.2.1 Patch 1

Gradle版本:

        gradle-8.4-bin.zip 

1.引用依赖

//room
    implementation 'androidx.room:room-runtime:2.6.1'
    annotationProcessor 'androidx.room:room-compiler:2.6.1'

 2.定义数据实体

/**
 * 用户类,用于表示用户信息
 */
@Entity
public class User {
    // 主键,自动生成
    @PrimaryKey(autoGenerate = true)
    private long id;
    private String name; // 用户名
    private String age; // 年龄
    private String sex; // 性别

    /**
     * 构造函数,用于创建一个新的用户实例
     * @param name 用户名
     * @param age 年龄
     * @param sex 性别
     */
    public User(String name, String age, String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    /**
     * 获取用户ID
     * @return 用户ID
     */
    public long getId() {
        return id;
    }

    /**
     * 设置用户ID
     * @param id 用户ID
     */
    public void setId(long id) {
        this.id = id;
    }

    /**
     * 获取用户名
     * @return 用户名
     */
    public String getName() {
        return name;
    }

    /**
     * 设置用户名
     * @param name 用户名
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取用户年龄
     * @return 用户年龄
     */
    public String getAge() {
        return age;
    }

    /**
     * 设置用户年龄
     * @param age 用户年龄
     */
    public void setAge(String age) {
        this.age = age;
    }

    /**
     * 获取用户性别
     * @return 用户性别
     */
    public String getSex() {
        return sex;
    }

    /**
     * 设置用户性别
     * @param sex 用户性别
     */
    public void setSex(String sex) {
        this.sex = sex;
    }
}

3.定义Dao类

/**
 * 用户数据访问接口,提供用户数据的增删改查操作。
 */
@Dao
public interface UserDao {

    /**
     * 插入用户信息到数据库。
     * 
     * @param user 需要插入的用户对象。
     */
    @Insert
    void insertUser(User user);

    /**
     * 根据用户名查找用户。
     * 
     * @param name 要查找的用户名,支持部分匹配。
     * @return 找到的第一个用户对象,如果没有找到返回null。
     */
    @Query("SELECT * FROM user WHERE name LIKE :name LIMIT 1")
    User findUserByName(String name);

    /**
     * 更新用户信息。
     * 
     * @param user 需要更新的用户对象。
     */
    @Update
    void updateUser(User user);

    /**
     * 根据用户名删除用户。
     * 
     * @param name 需要删除的用户的用户名。
     */
    @Query("DELETE FROM user WHERE name LIKE :name")
    void deleteUserByName(String name);

    /**
     * 查找所有用户信息。
     * 
     * @return 用户列表,包含所有用户。
     */
    @Query("SELECT * FROM user")
    List<User> findAllUsers();

    /**
     * 删除所有用户信息。
     */
    @Query("DELETE FROM User")
    void deleteAllUsers();

}

4.创建Database文件

/**
 * 应用的数据库类,继承自RoomDatabase。用于定义数据库的结构和操作。
 * 使用单例模式确保全局仅有一个数据库实例。
 */
@Database(entities = {User.class}, version = 1, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
    // 静态变量INSTANCE用于存储数据库的单例实例
    private static volatile AppDatabase INSTANCE;
    
    /**
     * 获取AppDatabase的单例实例。
     * 如果实例不存在,则通过Room的databaseBuilder创建一个新的实例。
     * 使用双重检查锁定确保线程安全。
     * 
     * @param context 上下文对象,用于访问应用的资源和其他组件。
     * @return AppDatabase的单例实例。
     */
    public static AppDatabase getDatabase(Context context) {
        if (INSTANCE == null) {
            synchronized (AppDatabase.class) {
                if (INSTANCE == null) {
                    // 通过Room的databaseBuilder构建数据库实例,配置数据库名称和实体类
                    INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
                                    AppDatabase.class, DATABASE_NAME)
                            .fallbackToDestructiveMigration() // 数据库升级时采用破坏性迁移策略
                            .build();
                }
            }
        }
        return INSTANCE;
    }
    
    /**
     * 获取UserDao的抽象方法。UserDao是一个接口,用于操作用户数据。
     * 需要在具体实现类中提供该方法的具体实现。
     * 
     * @return UserDao接口实例,用于进行用户数据的增删改查操作。
     */
    public abstract UserDao getUserDao();
}

5.定义Constant类

public class Constant {
    // 数据库名称常量
    public static final String DATABASE_NAME = "app_database";
    public static final String RESULT_MESSAGE_USER_NOT_EXIST = "用户不存在";
    public static final String RESULT_MESSAGE_USER_EXIST = "用户已存在";
    public static final String RESULT_MESSAGE_ERROR = "操作失败";
}

至此,User表已经建立完成,接下来开始数据库操作方法

需要说明的是

本文实现的demo基于Android MVVM设计模式,所以并不会在Activity中直接操作数据库,一般来说,与用户相关的数据操作的部分需要放在ViewModel层,同时建议定义一个Repository来处理数据库操作,这样的话,最终的调用模式就是:

1.Repository层处理数据库的增删改查操作

2.ViewModel层处理与用户相关的数据操作逻辑

3.Activity用来更新UI

这样做一方面满足了MVVM的设计模式,同时也减轻了ViewModel层的负担

那么继续分享代码:

6.数据库操作类

/**
 * 用户仓库类,负责用户数据的增删改查操作。
 */
public class UserRepository {
    private final UserDao userDao;

    /**
     * 构造函数,初始化用户数据访问对象。
     *
     * @param database 应用数据库实例。
     */
    public UserRepository(AppDatabase database) {
        this.userDao = database.getUserDao();
    }

    /**
     * 插入用户。如果用户已存在,则操作失败。
     *
     * @param user 要插入的用户对象。
     * @return 返回操作结果,成功或失败。
     */
    @Transaction
    public UserRepositoryResult insertUser(User user) {
        // 检查用户是否已存在
        if (userDao.findUserByName(user.getName()) != null) {
            return new UserRepositoryResult(
                    UserRepositoryResult.Type.FAILURE,
                    RESULT_MESSAGE_USER_EXIST);
        }
        userDao.insertUser(user);
        return UserRepositoryResult.success();
    }

    /**
     * 更新用户信息。如果用户不存在,则操作失败。
     *
     * @param user 要更新的用户对象。
     * @return 返回操作结果,成功或失败。
     */
    @Transaction
    public UserRepositoryResult updateUser(User user) {
        // 确认用户存在
        User findUser = userDao.findUserByName(user.getName());
        if (findUser == null) {
            return new UserRepositoryResult(
                    UserRepositoryResult.Type.FAILURE,
                    RESULT_MESSAGE_USER_NOT_EXIST);
        }
        user.setId(findUser.getId());
        userDao.updateUser(user);
        return UserRepositoryResult.success();
    }

    /**
     * 根据用户名删除用户。如果用户不存在,则操作失败。
     *
     * @param name 要删除的用户的用户名。
     * @return 返回操作结果,成功或失败。
     */
    @Transaction
    public UserRepositoryResult deleteUserByName(String name) {
        // 确认用户存在
        if (userDao.findUserByName(name) == null) {
            return new UserRepositoryResult(
                    UserRepositoryResult.Type.FAILURE,
                    RESULT_MESSAGE_USER_NOT_EXIST);
        }
        userDao.deleteUserByName(name);
        return UserRepositoryResult.success();
    }

    /**
     * 删除所有用户。
     *
     * @return 返回操作结果,成功。
     */
    @Transaction
    public UserRepositoryResult deleteAllUsers() {
        userDao.deleteAllUsers();
        return UserRepositoryResult.success();
    }

    /**
     * 根据用户名查找用户。如果用户不存在,则操作失败。
     *
     * @param name 要查找的用户的用户名。
     * @return 返回操作结果,包含查找到的用户列表,如果未找到则列表为空。
     */
    @Transaction
    public UserRepositoryResult findUserByName(String name) {
        User user = userDao.findUserByName(name);
        // 处理用户不存在的情况
        if (user == null) {
            return new UserRepositoryResult(
                    UserRepositoryResult.Type.FAILURE,
                    RESULT_MESSAGE_USER_NOT_EXIST);
        }
        List<User> list = new ArrayList<>();
        list.add(user);
        return UserRepositoryResult.success(list);
    }

    /**
     * 查找所有用户。
     *
     * @return 返回操作结果,包含所有用户列表。
     */
    @Transaction
    public UserRepositoryResult findAllUsers() {
        List<User> list = userDao.findAllUsers();
        return UserRepositoryResult.success(list);
    }
}

7.数据库操作结果类

/**
 * 用户仓库操作结果类,用于封装用户操作的结果信息。
 */
public class UserRepositoryResult {
    /**
     * 操作结果类型,包括成功和失败。
     */
    public enum Type {
        SUCCESS,
        FAILURE
    }

    private final Type type; // 操作结果类型
    private final String errorMessage; // 错误信息,当操作失败时使用

    // 成功操作时找到的用户列表
    private final List<User> foundUserList = new ArrayList<>();

    /**
     * 构造函数,用于创建一个操作结果实例。
     * 
     * @param type 操作结果类型(成功或失败)。
     * @param errorMessage 错误信息,如果操作成功,则可以为null。
     */
    public UserRepositoryResult(Type type, String errorMessage) {
        this.type = type;
        this.errorMessage = errorMessage;
    }

    /**
     * 构造函数,用于创建一个包含用户列表的操作结果实例。
     * 
     * @param type 操作结果类型(成功或失败)。
     * @param errorMessage 错误信息,如果操作成功,则可以为null。
     * @param foundUserList 找到的用户列表,如果操作没有找到用户,可以为null或空列表。
     */
    public UserRepositoryResult(Type type, String errorMessage, List<User> foundUserList) {
        this.type = type;
        this.errorMessage = errorMessage;
        if(null != foundUserList && !foundUserList.isEmpty()){
            this.foundUserList.addAll(foundUserList);
        }
    }

    /**
     * 获取操作结果类型。
     * 
     * @return 返回操作结果类型(成功或失败)。
     */
    public Type getType() {
        return type;
    }

    /**
     * 获取错误信息。
     * 
     * @return 如果操作成功,返回null;否则返回错误信息字符串。
     */
    public String getErrorMessage() {
        return errorMessage;
    }

    /**
     * 获取找到的用户列表。
     * 
     * @return 返回操作成功时找到的用户列表,可能为空。
     */
    public List<User> getFoundUserList() {
        return foundUserList;
    }

    /**
     * 创建一个表示操作成功的UserRepositoryResult实例。
     * 
     * @param foundUserList 找到的用户列表,可以为null。
     * @return 返回一个初始化为成功类型且包含指定用户列表的UserRepositoryResult实例。
     */
    public static UserRepositoryResult success(List<User> foundUserList) {
        return new UserRepositoryResult(Type.SUCCESS, null, foundUserList);
    }

    /**
     * 创建一个表示操作成功的UserRepositoryResult实例(不包含用户列表)。
     * 
     * @return 返回一个初始化为成功类型且不包含用户列表的UserRepositoryResult实例。
     */
    public static UserRepositoryResult success() {
        return new UserRepositoryResult(Type.SUCCESS, null, null);
    }

    /**
     * 创建一个表示操作失败的UserRepositoryResult实例。
     * 
     * @param errorMessage 错误信息字符串。
     * @return 返回一个初始化为失败类型且包含指定错误信息的UserRepositoryResult实例。
     */
    public static UserRepositoryResult failure(String errorMessage) {
        return new UserRepositoryResult(Type.FAILURE, errorMessage, null);
    }

}

8.ViewModel代码

/**
 * 主要的ViewModel类,用于处理与用户相关的数据操作。
 */
public class MainViewModel extends ViewModel {
    // 用户数据仓库接口
    private final UserRepository userRepository;
    // 执行器服务,用于在后台线程中执行数据库操作
    private static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();

    /**
     * 构造函数,初始化用户数据仓库。
     *
     * @param userRepository 用户数据仓库实例。
     */
    public MainViewModel(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    // 用于存储插入用户操作结果的LiveData对象
    private final MutableLiveData<UserRepositoryResult> insertUserResult = new MutableLiveData<>();

    /**
     * 获取插入用户操作的结果。
     *
     * @return UserRepositoryResult 插入操作的结果。
     */
    public LiveData<UserRepositoryResult> getInsertUserResult() {
        return insertUserResult;
    }

    // 用于存储更新用户操作结果的LiveData对象
    private final MutableLiveData<UserRepositoryResult> updateUserResult = new MutableLiveData<>();

    /**
     * 获取更新用户操作的结果。
     *
     * @return UserRepositoryResult 更新操作的结果。
     */
    public LiveData<UserRepositoryResult> getUpdateUserResult() {
        return updateUserResult;
    }

    // 用于存储根据名称删除用户操作结果的LiveData对象
    private final MutableLiveData<UserRepositoryResult> deleteUserByNameResult = new MutableLiveData<>();

    /**
     * 获取根据名称删除用户操作的结果。
     *
     * @return UserRepositoryResult 删除操作的结果。
     */
    public LiveData<UserRepositoryResult> getDeleteUserByNameResult() {
        return deleteUserByNameResult;
    }

    // 用于存储删除所有用户操作结果的LiveData对象
    private final MutableLiveData<UserRepositoryResult> deleteAllUsersResult = new MutableLiveData<>();

    /**
     * 获取删除所有用户操作的结果。
     *
     * @return UserRepositoryResult 删除操作的结果。
     */
    public LiveData<UserRepositoryResult> getDeleteAllUsersResult() {
        return deleteAllUsersResult;
    }

    // 用于存储根据名称查找用户操作结果的LiveData对象
    private final MutableLiveData<UserRepositoryResult> findUserByNameResult = new MutableLiveData<>();

    /**
     * 获取根据名称查找用户操作的结果。
     *
     * @return UserRepositoryResult 查找操作的结果。
     */
    public LiveData<UserRepositoryResult> getFindUserByNameResult() {
        return findUserByNameResult;
    }

    // 用于存储查找所有用户操作结果的LiveData对象
    private final MutableLiveData<UserRepositoryResult> findAllUsersResult = new MutableLiveData<>();

    /**
     * 获取查找所有用户操作的结果。
     *
     * @return UserRepositoryResult 查找操作的结果。
     */
    public LiveData<UserRepositoryResult> getFindAllUsersResult() {
        return findAllUsersResult;
    }

    /**
     * 在后台线程中执行用户数据操作。
     */

    // 插入用户
    public void insertUser(final User user) {
        EXECUTOR_SERVICE.execute(() -> {
            insertUserResult.postValue(userRepository.insertUser(user));
        });
    }

    // 更新用户
    public void updateUser(final User user) {
        EXECUTOR_SERVICE.execute(() -> {
            updateUserResult.postValue(userRepository.updateUser(user));
        });
    }

    // 根据名称删除用户
    public void deleteUserByName(final String name) {
        EXECUTOR_SERVICE.execute(() -> {
            deleteUserByNameResult.postValue(userRepository.deleteUserByName(name));
        });
    }

    // 删除所有用户
    public void deleteAllUsers() {
        EXECUTOR_SERVICE.execute(() -> {
            deleteAllUsersResult.postValue(userRepository.deleteAllUsers());
        });
    }

    // 根据名称查找用户
    public void findUserByName(final String name) {
        EXECUTOR_SERVICE.execute(() -> {
            findUserByNameResult.postValue(userRepository.findUserByName(name));
        });
    }

    // 查找所有用户
    public void findAllUsers() {
        EXECUTOR_SERVICE.execute(() -> {
            findAllUsersResult.postValue(userRepository.findAllUsers());
        });
    }

    /**
     * ViewModel工厂类,用于创建MainViewModel实例。
     */
    public static class Factory extends ViewModelProvider.NewInstanceFactory {
        // 用户数据仓库实例
        private final UserRepository userRepository;

        /**
         * 构造函数,初始化用户数据仓库。
         *
         * @param userRepository 用户数据仓库实例。
         */
        public Factory(UserRepository userRepository) {
            this.userRepository = userRepository;
        }

        /**
         * 创建MainViewModel实例。
         *
         * @param modelClass ViewModel的类类型。
         * @return MainViewModel 实例。
         */
        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            return (T) new MainViewModel(userRepository);
        }
    }

}

9.MainActivity调用

/**
 * 主活动类,负责管理应用程序的主要界面。
 */
public class MainActivity extends AppCompatActivity {

    private MainViewModel viewModel; // 视图模型,用于管理活动背后的业务逻辑
    private ActivityMainBinding binding; // 数据绑定实例,用于简化UI更新
    private UserRepository userRepository;

    /**
     * 在活动创建时调用。
     *shaoshao
     * @param savedInstanceState 如果活动之前被销毁,这参数包含之前的状态。如果活动没被销毁之前,这参数是null。
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 启用边缘到边缘的UI
        EdgeToEdge.enable(this);
        // 设置数据绑定
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        // 设置视图的内边距,以适应系统栏位的高度
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
        userRepository = new UserRepository(AppDatabase.getDatabase(MVVMApplication.getInstance()));
        // 初始化视图模型
        viewModel = new ViewModelProvider(this, new MainViewModel.Factory(userRepository)).get(MainViewModel.class);
        binding.setViewModel(viewModel);
        initListeners();
        initObserver();
    }

    /**
     * 初始化视图监听器。
     */
    private void initListeners() {
        // 清空输入框操作监听
        binding.btnClearEdit.setOnClickListener(v -> {
            clearEditText();
        });

        // 插入用户操作监听
        binding.btnInsert.setOnClickListener(v -> {
            User userChecked = checkUserInfo();
            if (userChecked != null) {
                viewModel.insertUser(userChecked);
            }
        });

        // 根据姓名查找用户操作监听
        binding.btnFindByName.setOnClickListener(v -> {
            String nameChecked = checkName();
            if (!nameChecked.isEmpty()) {
                viewModel.findUserByName(nameChecked);
            }
        });

        // 更新用户操作监听
        binding.btnUpdate.setOnClickListener(v -> {
            User userChecked = checkUserInfo();
            if (userChecked != null) {
                viewModel.updateUser(userChecked);
            }
        });

        // 根据姓名删除用户操作监听
        binding.btnDeleteByName.setOnClickListener(v -> {
            String nameChecked = checkName();
            if (!nameChecked.isEmpty()) {
                viewModel.deleteUserByName(nameChecked);
            }
        });

        // 查找所有用户操作监听
        binding.btnFindAll.setOnClickListener(v -> {
            viewModel.findAllUsers();
        });

        // 删除所有用户操作监听
        binding.btnDeleteAll.setOnClickListener(v -> {
            viewModel.deleteAllUsers();
        });

    }

    private void initObserver() {
        // 观察插入结果
        viewModel.getInsertUserResult().observe(this, result -> {
            if (result.getType() == UserRepositoryResult.Type.SUCCESS) {
                showToast("添加成功");
            } else {
                showToast(result.getErrorMessage());
            }
        });

        // 观察查找结果
        viewModel.getFindUserByNameResult().observe(this, result -> {
            if (result.getType() == UserRepositoryResult.Type.SUCCESS) {
                if (!result.getFoundUserList().isEmpty()) {
                    User user = result.getFoundUserList().get(0);
                    binding.etName.setText(user.getName());
                    binding.etAge.setText(user.getAge());
                    binding.etSex.setText(user.getSex());
                } else {
                    showToast("未找到该用户");
                }
            } else {
                showToast(result.getErrorMessage());
            }
        });

        // 观察更新结果
        viewModel.getUpdateUserResult().observe(this, result -> {
            if (result.getType() == UserRepositoryResult.Type.SUCCESS) {
                showToast("更新成功");
            } else {
                showToast(result.getErrorMessage());
            }
        });

        // 观察删除结果
        viewModel.getDeleteUserByNameResult().observe(this, result -> {
            if (result.getType() == UserRepositoryResult.Type.SUCCESS) {
                showToast("删除成功");
            } else {
                showToast(result.getErrorMessage());
            }
        });

        // 观察查找所有用户的结果
        viewModel.getFindAllUsersResult().observe(this, result -> {
            if (result.getType() == UserRepositoryResult.Type.SUCCESS) {
                List<User> userList = result.getFoundUserList();
                if (!userList.isEmpty()) {
                    StringBuilder sb = new StringBuilder();
                    for (User user : userList) {
                        sb.append(user.getName()).append(" ").append(user.getAge()).append(" ").append(user.getSex()).append("\n");
                    }
                    showToast(sb.toString());
                } else {
                    showToast("没有用户");
                }
            } else {
                showToast(result.getErrorMessage());
            }
        });

        // 观察删除所有用户的结果
        viewModel.getDeleteAllUsersResult().observe(this, result -> {
            if (result.getType() == UserRepositoryResult.Type.SUCCESS) {
                showToast("删除成功");
            } else {
                showToast(result.getErrorMessage());
            }
        });
    }

    // 封装对用户信息输入的验证
    private User checkUserInfo() {
        String name = binding.etName.getText().toString();
        if (name.isEmpty()) {
            showToast("请输入姓名");
            return null;
        }
        String age = binding.etAge.getText().toString();
        if (age.isEmpty()) {
            showToast("请输入年龄");
            return null;
        }
        String sex = binding.etSex.getText().toString();
        if (sex.isEmpty()) {
            showToast("请输入性别");
            return null;
        }
        return new User(name, age, sex);
    }

    // 封装对姓名输入的检查
    private String checkName() {
        String name = binding.etName.getText().toString();
        if (name.isEmpty()) {
            showToast("请输入姓名");
            return "";
        }
        return name;
    }

    // 清除编辑文本框中的内容
    private void clearEditText() {
        binding.etName.setText("");
        binding.etAge.setText("");
        binding.etSex.setText("");
    }

    // 简化Toast消息的显示
    private void showToast(String message) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
    }

}

10.布局文件 

<?xml version="1.0" encoding="utf-8"?>
<!--使用databinding功能,根布局需要使用<layout>标签 -->
<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 Binding的<data>标签,用于定义布局中使用的数据对象和表达式-->
    <data>
        <variable
            name="viewModel"
            type="com.example.mvvmdemo.ui.main.MainViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ui.main.MainActivity">

        <EditText
            android:id="@+id/et_name"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:layout_marginEnd="10dp"
            android:ems="10"
            android:hint="姓名"
            android:inputType="text"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.05" />

        <EditText
            android:id="@+id/et_age"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:layout_marginTop="10dp"
            android:layout_marginEnd="10dp"
            android:ems="10"
            android:hint="年龄"
            android:inputType="text"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/et_name" />

        <EditText
            android:id="@+id/et_sex"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:layout_marginTop="10dp"
            android:layout_marginEnd="10dp"
            android:ems="10"
            android:hint="性别"
            android:inputType="text"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/et_age" />

        <Button
            android:id="@+id/btn_insert"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:layout_marginTop="10dp"
            android:layout_marginEnd="10dp"
            android:text="增加"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="1.0"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/btn_clear_edit" />

        <Button
            android:id="@+id/btn_find_by_name"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:layout_marginTop="10dp"
            android:layout_marginEnd="10dp"
            android:text="根据姓名查询"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/btn_insert" />

        <Button
            android:id="@+id/btn_update"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:layout_marginTop="10dp"
            android:layout_marginEnd="10dp"
            android:text="更新"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.0"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/btn_find_by_name" />

        <Button
            android:id="@+id/btn_find_all"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:layout_marginTop="10dp"
            android:layout_marginEnd="10dp"
            android:text="查询所有"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.0"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/btn_delete_by_name" />

        <Button
            android:id="@+id/btn_delete_by_name"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:layout_marginTop="10dp"
            android:layout_marginEnd="10dp"
            android:text="根据姓名删除"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/btn_update" />

        <Button
            android:id="@+id/btn_delete_all"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:layout_marginTop="10dp"
            android:layout_marginEnd="10dp"
            android:layout_marginBottom="80dp"
            android:text="清空数据表"
            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/btn_find_all"
            app:layout_constraintVertical_bias="0.0" />

        <Button
            android:id="@+id/btn_clear_edit"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:text="清空输入"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/et_sex" />
    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

写在最后 

        其实关于MVVM的代码并不好写,因为MVVM是一种编程思想,并没有确定的写法和流派,所以建议每一个学习MVVM的同学,不要只是盲目的死扣形式,而是要追求代码的内在。不管是何种形式,只要是使得代码条理更清晰,功能稳定,就是好代码。代码这种东西,没有尽头,今天我们学习MVVM设计模式,可能不知道什么时候,又兴起来一种新的形式,就像潮起潮落一样。

然后关于我上面展示的代码,我没有做过多的解释,主要是快下班了,我今天太想要把这篇积压已久的文章整理好发出去,哈哈。不过也附带了足够多的代码注释,如果你真的很希望掌握Room的用法,也建议你能够对照着代码多看,然后在自己的开发环境里多运行几次,然后把demo进行一些功能扩展和修改,相信你一定可以掌握的很好。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/560784.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Postgres数据库中的死锁是如何产生的,如何避免和解决?

文章目录 死锁的产生原因如何避免死锁如何解决死锁示例代码查询死锁信息终止事务 在Postgres数据库中&#xff0c;死锁是一种特殊的情况&#xff0c;其中两个或多个事务相互等待对方释放资源&#xff0c;从而导致它们都无法继续执行。这种情况通常发生在多个事务尝试以不同的顺…

Clark Transform的FPGA代码实现讲解

Clark 变换是坐标转换&#xff0c;将输入的三相电流转换到两相直角坐标下电流&#xff0c;如下图为坐标表示方法。 根据坐标的投影我们可以得到 从而可以推知&#xff1a; 上述公式为最终代码中实现的计算公式。 在FPGA中实现时&#xff0c;由于FPGA中不擅长浮点数计算&#xf…

Leetcode刷题-(26~35)-Java

算法是码农的基本功&#xff0c;也是各个大厂必考察的重点&#xff0c;让我们一起坚持写算法题吧。 遇事不决&#xff0c;可问春风&#xff0c;春风不语&#xff0c;即是本心。 我们在我们能力范围内&#xff0c;做好我们该做的事&#xff0c;然后相信一切都事最好的安排就可…

数据结构之排序了如指掌(三)

目录 题外话 正题 快速排序 Hoare法 Hoare法思路 Hoare法代码详解 挖坑法 挖坑法思路 挖坑法代码 前后指针法 前后指针法思路 前后指针法代码 小结 题外话 我们接着把没有写完的排序内容完成,快速排序其实大同小异,大家好好把思路整理一下 正题 快速排序 快速排序一…

WideDeep

这里写目录标题 1. 背景2. 贡献3 模型结构&#xff08;1&#xff09;任务定义&#xff08;2&#xff09;The Wide Component&#xff08;3&#xff09;The Deep Component&#xff08;4&#xff09;联合训练Wide和Deep Model 4. 参考 1. 背景 (1) 广义线性回归通常被用于推荐模…

树莓派+Openwrt连接校园网,打破校园网设备限制

前言 因为本校学生校园网只允许最多三个设备登录&#xff0c;对于同时拥有多个联网设备的我十分不友好&#xff0c;而且大多单片机如esp32的wifi模块是只允许一般的WPA/WPA2认证的&#xff0c;是不支持校园网的portal认证。所以我决定搞一个路由器。 然后我上网买了一个TP-Li…

Android Studio 新建Android13 代码提示Build Tools revision XX is corrupted无法编译解决

Android Studio 新建Android13 代码提示Build Tools revision XX is corrupted无法编译解决 文章目录 Android Studio 新建Android13 代码提示Build Tools revision XX is corrupted无法编译解决一、前言二、分析解决1、原因分析2、解决方法 三、其他1、Android13 新项目无法编…

采用matplotlib可视化kitti

配置kitti_object_vis没成功&#xff0c;用kitti_object_vis的一些函数加上matplotlib进行可视化 import numpy as np import matplotlib.pyplot as pltimport numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D def roty(t):"&quo…

JavaWeb-登录校验

会话技术 浏览器使用的是http协议&#xff0c;多次请求间数据是不能共享的&#xff0c;例如我们要去访问用户数据的接口&#xff0c;但这时候用户是否已经登入了呢&#xff1f;是不知道的&#xff0c;为了解决这个问题&#xff0c;于是引入了会话跟踪技术。 会话&#xff1a;…

05—js对象

一、初识对象 JavaScript是面向对象编程&#xff08;Object Oriented Programming&#xff0c;OOP&#xff09;语言。 面对象是一种复合值&#xff1a;它将很多值集合在一起&#xff0c;可通过名字访问这些值。对象也可看做一种无序的数据集合&#xff0c;由若干个“键值对”…

iced 入门一

&#x1f4d5;作者简介&#xff1a; 过去日记&#xff0c;致力于Java、GoLang,Rust等多种编程语言&#xff0c;热爱技术&#xff0c;喜欢游戏的博主。 &#x1f4d8;相关专栏Rust初阶教程、go语言基础系列、spring教程等&#xff0c;大家有兴趣的可以看一看 &#x1f4d9;Jav…

基于ssm的企业在线培训系统论文

摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装企业在线培训系统软件来发挥其高效地信息处理的作用&#x…

每日一题

腐烂的苹果_牛客题霸_牛客网 思路分析:广度优先遍历&#xff0c;找到所有腐烂的苹果同时向四方扩散&#xff0c;就是第一轮把所有腐烂的苹果加入队列中&#xff0c;这就跟MQ的消息队列的原理差不多&#xff0c;第一次记录队列的长度&#xff0c;广度遍历一次&#xff0c;长度--…

第一个STM32F767IGT6核心板

一. 制作原因 起先是因为参加计算机设计大赛准备的板子&#xff0c;其作用是连接OV5640摄像头来识别车牌号&#xff0c;主要外设有摄像头&#xff0c;SDRAM&#xff0c;网口等。 二. 原理图和PCB 原理图 PCB 三. 测试 1. 测试SDRAM功能 按下按键我们可以在串口中看到内存…

【基础IO】谈谈动静态库(怒肝7000字)

文章目录 前言实验代码样例静态库生成一个静态库归档工具ar静态库的链接 动态库创建动态库加载动态库 动静态链接静态链接动态链接动静态链接的优缺点 前言 在软件开发中&#xff0c;库&#xff08;Library&#xff09;是一种方式&#xff0c;可以将代码打包成可重用的格式&…

【C语言】内存函数-memcpy-memmove-memset...用法及实现,沉淀自己!

&#x1f525;博客主页&#x1f525;&#xff1a;【 坊钰_CSDN博客 】 欢迎各位点赞&#x1f44d;评论✍收藏⭐ 目录 1. memcpy函数使用和模拟实现 2. memmove使用和模拟实现 3. memset函数的使用 4. memcmp函数的使用 1. memcpy函数使用和模拟实现 <string.h>-------…

机器学习理论基础—神经网络算法公式学习

机器学习理论基础—神经网络公式学习 M-P神经元 M-P神经元&#xff08;一个用来模拟生物行为的数学模型&#xff09;&#xff1a;接收n个输入(通常是来自其他神经 元)&#xff0c;并给各个输入赋予权重计算加权和&#xff0c;然后和自身特有的阈值进行比较 (作减法&#xff0…

pytorch-MNIST测试实战

这里写目录标题 1. 为什么test2. 如何做test3. 什么时候做test4. 完整代码 1. 为什么test 如下图&#xff1a;上下两幅图中蓝色分别表示train的accuracy和loss&#xff0c;黄色表示test的accuracy和loss&#xff0c;如果单纯看train的accuracy和loss曲线就会认为模型已经train…

【优质书籍推荐】Vue.js+Node.js全栈开发实战

大家好&#xff0c;我是爱编程的喵喵。双985硕士毕业&#xff0c;现担任全栈工程师一职&#xff0c;热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。…

von Mises-Fisher Distribution (代码解析)

torch.distribution 中包含了很多概率分布的实现&#xff0c;本文首先通过均匀分布来说明 Distribution 的具体用法, 然后再解释 von Mises-Fisher 分布的实现, 其公式推导见 von Mises-Fisher Distribution. 1. torch.distribution.Distribution 以下是 Uniform 的源码: cl…