Android Jetpack学习系列——Room

news2024/11/28 18:53:36

关于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.定义数据实体

/**
 * 用户类,用于表示用户信息
 */
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类

/**
 * 用户数据访问接口,提供用户数据的增删改查操作。
 */
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进行一些功能扩展和修改,相信你一定可以掌握的很好。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

拉普拉斯金字塔的频谱分析

1. 基本分析 拉普拉斯金字塔分解&#xff0c;主要由以下步骤组成&#xff1a; 对输入图像 L0 进行低通滤波&#xff0c;其中常采用高斯滤波&#xff1b;对低通滤波后的图像进行 1/2 倍率的下采样&#xff0c;这里的下采样通常是指直接取偶行且偶列&#xff08;以 0 开始计&am…

Linux安装部署Tomcat

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ Linux安装部署Tomcat //将tomcat压缩包解压到对…

【计算机网络】常用编码方式+例题(曼彻斯特编码、差分曼彻斯特编码...)

常用编码方式例题 常用编码方式练习画出四种编码20221题342015题342013题34 常用编码方式 练习 画出四种编码 20221题34 这个题目的考察是差分曼彻斯特编码。 差分曼彻斯特编码在每个码元的中间时刻电平都会发生跳变。与曼彻斯特编码不同的是&#xff1a;电平的跳变仅代表时钟…

4G/5G布控球/移动执法仪/智能单兵电力巡检远程视频智能监控方案

一、背景与需求 随着科技的不断进步&#xff0c;视频监控技术已成为电力行业不可或缺的一环。电力行业的巡检及建设工作&#xff0c;因施工现场在人迹罕见的野外或山区&#xff0c;地形复杂多变&#xff0c;安全更是重中之重&#xff0c;现场工作的视频图像需实时传回监管中心…

SpringCloud、SpringBoot、JDK版本对应关系

SpringCloud与SpringBoot 版本 官网说明&#xff1a;https://spring.io/projects/spring-cloud#overview SpringBoot 与 JDK版本关系 发布说明&#xff1a;https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Release-Notes SpringBoot 3.x不再支持JDK1.…

[leetcode] 54. 螺旋矩阵

文章目录 题目描述解题方法模拟java代码复杂度分析 相似题目 题目描述 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a;…

每日两题 / 15. 三数之和 73. 矩阵置零(LeetCode热题100)

15. 三数之和 - 力扣&#xff08;LeetCode&#xff09; 先确定一个数t&#xff0c;对于剩下的两个数&#xff0c;要求两数之和为t的负数 三数之和就退化成了两数之和&#xff0c;两数之和可以用双指针 先排序&#xff0c;左右两个指针&#xff0c;指向的数之和大于目标值&…

PCB裸板如何测试?当然是这些....

在印刷电路板制造中&#xff0c;可能会遇见裸板测试&#xff08;主要用于监测PCB在生产过程中可能出现的缺陷&#xff0c;如开路、短路、错位等&#xff0c;以此确保后续装配和使用的顺利进行&#xff09;。本文将介绍几种常用的PCB裸板测试方案&#xff0c;希望对小伙伴们有所…

AD高速板设计(笔记)

Alt左键高亮某个器件或属性&#xff0c;点击任意位置取消高亮。 TP设置旋转角度为45度&#xff0c;即可选中器件按空格旋转时候每次旋转45度。 先画出想要割槽的区域&#xff0c;选中之后TVB即可开槽。 左右翻转电路板&#xff1a;VB DR打开规则设置 UFO对器件进行扇出&#…

LangChain入门:20.探索使用 Self-Ask with Search 代理

引言 在信息爆炸的时代&#xff0c;准确快速地找到事实性问题的答案变得尤为重要。LangChain的Self-Ask with Search代理&#xff08;SELF_ASK_WITH_SEARCH&#xff09;正是为了解决这一挑战而生。本文将深入探讨这一代理的工作原理&#xff0c;并通过实例演示其如何巧妙地处理…

【六】fastapi+vue前后端分离项目

前端代码 https://gitee.com/feiminjie/helloworldfront 后端代码 https://gitee.com/feiminjie/helloworld 整体效果 首页 用例管理页 用例详情页

TM1621E 驱动程序

TM1621E 驱动程序 TM1621E 芯片引脚TM1621E 发送数据TM1621E 发送指令TM1621E 写入时序屏幕真值表完整驱动显示函数 TM1621E 芯片引脚 CS——片选引脚&#xff0c;低电平有效。 WR——数据输入&#xff0c;在WR信号的上升沿&#xff0c;DATA 线上的数据写到TM1621E。 DATA——…

健身管理小程序|基于微信开发健身管理小程序的系统设计与实现(源码+数据库+文档)

健身管理小程序目录 基于微信开发健身管理小程序设计与实现 一、前言 二、系统设计 三、系统功能设计 小程序端&#xff1a; 后台 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码…

Java-通过Maven导入本地jar包的常用方式

1.首先创建一个用于创建jar包的项目&#xff0c;进行测试 2.测试成功后进行项目打包 3.创建一个要导入本地jar包的项目&#xff0c;在项目下创建lib目录&#xff0c;并将刚才打包好的jar包复制进去 4.在pom.xml文件中引入 5.运行测试

fatal: Out of memory, malloc failed

git 切分支&#xff0c;或者clone仓库的时候碰到这个错误&#xff0c;看网上很多配置config文件&#xff0c;都尝试了没效果。 自测了一个可行的方式&#xff1a; 由于本人用的sourcetree 所以解决方式如下&#xff1a; git升级为最新版&#xff0c;选择系统GIt版本&#xf…

Qt编译静态链接库和动态链接库(WindowsLinux)

最近需要在Linux中编译相关程序动态库&#xff0c;对相关经验总结。 使用到的工具为Qt5.13.2 QT编译 1. WINDOWS1.1 静态库制作1.2 静态库的使用1.3 动态库制作1.4 动态库使用 2. LINUX2.1 静态链接库2.2动态库 附录gcc 编译程序查找顺序&#xff1a;Linux 程序运行时查找顺序&…

【静态分析】软件分析课程实验A1-活跃变量分析和迭代求解器

1 作业导览 为 Java 实现一个活跃变量分析&#xff08;Live Variable Analysis&#xff09;。实现一个通用的迭代求解器&#xff08;Iterative Solver&#xff09;&#xff0c;用于求解数据流分析问题&#xff0c;也就是本次作业中的活跃变量分析。 Live Variable Analysis 详…

Midjourney常见玩法及prompt关键词技巧

今天系统给大家讲讲Midjourney的常见玩法和prompt关键词的一些注意事项&#xff0c;带大家入门&#xff5e;&#xff08;多图预警&#xff0c;建议收藏&#xff5e;&#xff09; 一、入门及常见玩法 1、注册并添加服务器&#xff08;会的童鞋可跳过&#xff5e;&#xff09; …

Jmeter分布式压测操作流程

分布式压测原理 分布式压测操作 保证本机和执行机的JDK和Jmeter版本一致配置Jmeter环境变量配置Jmeter配置文件 上传每个执行机服务jmeter chmod -R 755 apache-jmeter-5.1.1/ 执行机配置写自己的ip 控制机配置所有执行机ip,把server.rmi.ssl.disable改成true 将本机也作为压…

骨传导耳机哪个品牌最好?六大压箱底干货,一看便知!

在当今的蓝牙耳机市场&#xff0c;骨传导耳机以其独特的佩戴方式逐渐晋升为耳机界的新宠。然而&#xff0c;随着骨传导耳机逐渐成为热门款式&#xff0c;市场上的骨传导耳机品牌琳琅满目&#xff0c;消费者在选购时往往感到无从下手。其中&#xff0c;还经常有人入手耳机后出现…