【问题标题】:confused about Populating AutoCompleteTextView Suggestions with a Room Database对使用房间数据库填充 AutoCompleteTextView 建议感到困惑
【发布时间】:2021-12-09 12:11:35
【问题描述】:

我是一个新手,并试图通过本教程在我的 JAVA android 应用程序中实现 autoComplete 功能(不是 Kotlin,因为我还没有了解它):

link

用户可以在自动填充文本视图中搜索城市名称,当他们输入时,建议应根据他们输入的内容显示在下拉列表中,例如,如果他们输入“N”,则建议应显示“新约克”、“新城堡”等

建议应该预先填充数千个城市信息的现有数据库(数据已经在里面),我将这个 city_db.db 文件保存在我的资产文件夹中。

我有几个问题:

  1. 在写My DAO.java类的时候,我应该包含我的查询命令,我只包含查询部分,因为我只需要从数据库中读取,而不是写入,所以我写了:
package com.example.roomtest;
import androidx.room.Dao;
import androidx.room.Query;
@Dao
public interface DAO {
    //this class contains methods that accesses the database
    @Query("SELECT * city_name FROM city")
}

但它只是显示红线并告诉我“此处不允许注释”

我不确定这是否是正确的查询命令,不应该是这样的

"SELECT * city_name FROM city where like "what the user typed%" "?
  1. 我不确定是否需要更多类来实现这个自动完成功能, 现在我有这些课程:

1.MainActivity.java

2.Entity.java

3.Dao.java

4.Database.java

我需要更多的类来实现这个自动完成功能吗?我想我错过了什么。

这是我在所有课程中的代码:


package com.example.roomtest;
    import androidx.appcompat.app.AppCompatActivity;
    import android.os.Bundle;
    import android.widget.AutoCompleteTextView;
    import android.widget.EditText;
    
    public class MainActivity extends AppCompatActivity {
        private AutoCompleteTextView at;
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            at = findViewById(R.id.att);
        }
    }

package com.example.roomtest;
    import android.content.Context;
    import androidx.room.Room;
    import androidx.room.RoomDatabase;
    
    @androidx.room.Database(entities = Entity.class,version = 1)
    //must include the entity associated with database
    public abstract class Database extends RoomDatabase {
        //must be abstract
        public abstract DAO dao();
        //must declare this
        //check if a database instance is existing
        //if not, create one and return it
        private static volatile Database db;
        static Database getDatabase(final Context context){
            if(db==null){
                synchronized (Database.class){
                    if(db == null){
                        db =Room.databaseBuilder(context.getApplicationContext(),
                                Database.class, "Database")
                                .createFromAsset("city_db.db").build();
                    }
                }
            }
            return db;
        }
    }
package com.example.roomtest;
    
    import androidx.room.ColumnInfo;
    import androidx.room.PrimaryKey;
    
    
    @androidx.room.Entity(tableName = "city")
    public class Entity {
        //define data types in this class
        @PrimaryKey
        public int id;
        @ColumnInfo(name = "city_name")
        public String name;
    
        public String state;
        public String country;
        public String lon;
        public String lat;
    }
package com.example.roomtest;
    import androidx.room.Dao;
    import androidx.room.Query;
    @Dao
    public interface DAO {
        //this class contains methods that accesses the database
        @Query("SELECT * city_name FROM city")
    }

我的 gradle 依赖项:

dependencies {
    //room components
    def room_version = "2.3.0"
    implementation "androidx.room:room-runtime:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version"
    testImplementation "androidx.room:room-testing:$room_version"

    implementation 'androidx.appcompat:appcompat:1.3.1'
    implementation 'com.google.android.material:material:1.4.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

这里还有我的数据库的属性:

很抱歉发了这么长的帖子,但是我对这个简单功能所需的所有工作感到困惑,如果有人知道这方面的好教程,请与我分享,谢谢。

【问题讨论】:

    标签: java android-studio autocomplete android-room android-jetpack


    【解决方案1】:

    您需要在列名之间使用逗号(* 表示所有列,因此不需要 *,city_name)。

    SELECT * FROM city.... 在您的情况下相当于说SELECT id,city_name,state,country,lon,lat FROM city ....

    虽然SELECT *,city_name .... 说的是SELECT id,city_name,state,country,lon,lat,city_name FROM city ....

    虽然有效的 SQL 为什么两次得到相同的值(修辞)?此外,Room 应该使用哪个(即使它们相同)city_name(rehtorical)? 这是模棱两可的,有可能导致错误(我认为房间会处理这个并选择最后一个)

    你想要:-

    @Dao
    public interface DAO {
        //this class contains methods that accesses the database
        @Query("SELECT * FROM city WHERE city_name LIKE :cityName")
        List<Entity> getCity(String cityName); //<<<< The method that is called from the code
    }
    

    这将返回一个列表,即实体对象的列表

    或者:-

    @Dao
    public interface DAO {
        //this class contains methods that accesses the database
        @Query("SELECT city_name FROM city WHERE city_name LIKE :cityName")
        List<String> getCityName(cityName);
    }
    
    • 这会返回一个列表,其中每个字符串都是城市名称的
    • 你可以两者兼得

    在调用时,如果传递的字符串 (cityName) 是“New%”,那么您将得到 New York、Newcastle 等(即 % 是 1 个或多个字符的通配符 _ 对于单个字符)

    示例

    使用您的代码,稍作改动,上面的代码编译并运行正常。

    上面建议的更改已包含(两者),因此使用的 DAO 类是:-

    @Dao
    public interface DAO {
    
        //this class contains methods that accesses the database
        @Query("SELECT * FROM city WHERE city_name LIKE :cityName")
        List<Entity> getCitiesByName(String cityName);
    
        @Query("SELECT city_name FROM city WHERE city_name LIKE :cityName")
        List<String> getCityNamesByName(String cityName);
    }
    

    我对 Database 类 进行了一些更改,以 a) 允许在主线程上运行(添加 .allowMainThreadQueries())和 b) 不从资产文件创建(注释掉 .createFromAsset("city_db.db") ) 根据:-

    @androidx.room.Database(entities = Entity.class,version = 1)
    //must include the entity associated with database
    public abstract class Database extends RoomDatabase {
        //must be abstract
        public abstract DAO dao();
        //must declare this
        //check if a database instance is existing
        //if not, create one and return it
        private static volatile Database db;
        static Database getDatabase(final Context context){
            if(db==null){
                synchronized (Database.class){
                    if(db == null){
                        db = Room.databaseBuilder(context.getApplicationContext(),
                                Database.class, "Database")
                                //.createFromAsset("city_db.db")
                                .allowMainThreadQueries()
                                .build();
                    }
                }
            }
            return db;
        }
    }
    

    使用的 MainActivity 是:-

    public class MainActivity extends AppCompatActivity {
    
        Database db;
        DAO dao;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            db = Database.getDatabase(this);
            dao = db.dao();
            dao.getCitiesByName("Somewhere%");
            dao.getCityNamesByName("Nowhere%");
        }
    }
    

    因此,即使没有对结果进行任何处理(数据库内没有数据),上面也会打开数据库(不使用 .createFromAsset 来创建它)。

    您可以根据 Android Studio 中的 App Inspection(又名 Database Inspector)看到这一点:-

    因此您的代码,禁止资产文件的副本在按照建议进行修改时工作。

    存在的问题

    根据您的屏幕截图,当涉及到.build() 时,您可能会遇到持续的问题。

    房间期望复制的数据库与从实体派生的架构完全匹配(对于@Database 注释的实体参数中定义的实体)。

    作为一个例子,屏幕截图显示城市的名称在名为 name 的列(字段)中,而在您的实体中等效的列名,根据实体 Entity 是 city_name .房间构建将失败。 coord.lon 和 coord.lat 也是如此,它们是 lon 和 lat。

    此外,列定义(列类型和约束,例如 NOT NULL)也必须匹配,否则 Room 构建将失败。

    因此,您必须确保架构匹配。这可能需要使用一种可用的工具来相应地转换数据库(由于 Room 施加的限制,转换正在复制的数据库可能比尝试匹配现有数据库容易得多)。

    当您使用包含实体的有效/可用@Database 进行编译时,Room 会生成 SQL。

    例如在您的情况下,生成的 java(从 Android Studio 中的 Android View 可见)中的类与具有 @Database 注释但后缀为 _IMPL 的类命名相同。

    因此,当您的@Database 类被命名为 Database 时,就会有一个类 Database_IMPL。

    在类中会有一个方法createAllTables,它包含所有表的SQL(任何与room_打交道的SQL都可以忽略)。

    例如你的会是这样的:-

    您很可能需要使用 SQLite 工具转换数据库,例如 DB Browser for SQLite、SQlite Studio、DBeaver、Navicat For SQlite 才能使用架构。

    粗略地说,使用从生成的java中获取的SQL来创建表,然后复制数据。

    转换本身非常简单。首先创建 city_db.db 文件的副本。

    打开任何一个(在您喜欢的 SQLite 工具中,用于演示)

    • 我已经使用 Navicat 并使用查询来生成原始示例(只有几个国家)):-

      CREATE TABLE IF NOT EXISTS city (id INTEGER PRIMARY KEY, name TEXT,state TEXT, country TEXT, `coord.lon` REAL, `coord.lat`);
      INSERT INTO city VALUES
          (3904809,'Santa Rita','','BO',-63.3499,-17.966),
          (3904890,'Santa Elenta','','BO',-64.7833,-20.5496)
      ;
      
      - don't use the above
      
      

    然后使用下面的 SQL 注意这将删除原始表(不想要过大的资产文件)。 如果第二次运行,这将失败,因为原始列名已被有效覆盖,因此原始列名不存在。

    DROP TABLE IF EXISTS room_city;
    
    /*As copied from the App's generated java */
    /* BUT table name changed to room_city from city */
    CREATE TABLE IF NOT EXISTS `room_city` (`id` INTEGER NOT NULL, `city_name` TEXT, `state` TEXT, `country` TEXT, `lon` TEXT, `lat` TEXT, PRIMARY KEY(`id`));
    INSERT INTO room_city SELECT id,name AS city_name, state, country, `coord.lon` AS lon, `coord.lat` AS lat FROM city;
    /* To clean up so as to not include the original in the asset
        you don't want the asset hold twice the data that it needs to
        !!!!!NOTE!!!!! you should use a copy of the original city database
    */
    DROP TABLE IF EXISTS city;
    ALTER TABLE `room_city` RENAME TO `city`;
    VACUUM;
    /* Below not needed but good to check all is as expected */
    SELECT * FROM city;
    SELECT * FROm sqlite_master;
    
    • 请注意,这假定原始数据库与屏幕截图一致。

    • VACUUM 显示后的第一个 SELECT:-

    • 第二个显示架构:-

      • 它与 Room 预期的模式匹配

    应该关闭数据库和连接(打开检查并再次关闭以再次检查要复制的文件是否已保存)。

    然后将文件复制到资产文件夹中(您可能需要创建它),确保它被命名(可以重命名)city_db.db。 :-

    在使用上述演示的情况下,Database 类已更改为现在包含行 .createFromAsset("city_db.db") // &lt;&lt;&lt;&lt;&lt; reinstated

    MainActivity 更改为:-

    public class MainActivity extends AppCompatActivity {
    
        Database db;
        DAO dao;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            db = Database.getDatabase(this);
            dao = db.dao();
    
            for(Entity city: dao.getCitiesByName("%")) { //* <<<<<< changed to show all as only 2 cities in demo
                Log.d("CITYINFO-1","CityName is " + city.name + " state/county is " + city.state + " etc.....");
            }
            for(String cityname: dao.getCityNamesByName("%")) { //* <<<<<< changed to show all as only 2 cities in demo
                Log.d("CITYINFO-2","CityName is " + cityname);
            }
            dao.getCityNamesByName("New%");
        }
    }
    

    运行时日志显示:-

    2021-10-23 15:44:37.480 D/CITYINFO-1: CityName is Santa Rita state/county is  etc.....
    2021-10-23 15:44:37.480 D/CITYINFO-1: CityName is Santa Elenta state/county is  etc.....
    2021-10-23 15:44:37.482 D/CITYINFO-2: CityName is Santa Rita
    2021-10-23 15:44:37.482 D/CITYINFO-2: CityName is Santa Elenta
    

    所以数据库工作正常。

    如果您不转换会怎样?

    再次基于屏幕截图,然后应用程序失败:-

    2021-10-23 15:50:53.826 6345-6345/a.a.so69683821cities E/AndroidRuntime: FATAL EXCEPTION: main
        Process: a.a.so69683821cities, PID: 6345
        java.lang.RuntimeException: Unable to start activity ComponentInfo{a.a.so69683821cities/a.a.so69683821cities.MainActivity}: java.lang.IllegalStateException: Pre-packaged database has an invalid schema: city(a.a.so69683821cities.Entity).
         Expected:
        TableInfo{name='city', columns={country=Column{name='country', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, city_name=Column{name='city_name', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, lon=Column{name='lon', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'}, state=Column{name='state', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, lat=Column{name='lat', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]}
         Found:
        TableInfo{name='city', columns={country=Column{name='country', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, name=Column{name='name', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null'}, state=Column{name='state', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, coord.lat=Column{name='coord.lat', type='', affinity='1', notNull=false, primaryKeyPosition=0, defaultValue='null'}, coord.lon=Column{name='coord.lon', type='REAL', affinity='4', notNull=false, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]}
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2913)
    

    其中 EXPECTED 是 Room 根据 Entity 类期望架构是什么,但 Room 发现资产中的架构不匹配。

    【讨论】:

    • 谢谢你的回复,我的理解是:这个getCityName()方法会把用户输入作为cityName,用它在我的数据库中查询,然后将结果作为一个字符串列表返回,这将成为用户看到的下拉列表,这是正确的吗?注释错误消失了,但现在它显示它无法解析'city'和'city_name',你有这个提示吗? (它们在我的实体类中)
    • 另外,我不明白“列名之间的逗号”,我应该在哪个类中加逗号?
    • @Query("SELECT city_name FROM city WHERE city_name LIKE :city_name") List getCity(String cityName);现在看起来像这样,但查询中的“city_name”和“city”显示错误
    • 尝试编译,即CTRL+F9,如果编译OK就可以了。重新列名和逗号 SQL(查询必须遵循 sqlite.org/lang_select.html 的语法)。 * 是结果列(* 表示所有列的特殊代码)。尝试使用 SELECT * city_name 在 * 和 city_name 之间没有逗号。您将使用 SELECT *,city_name。但是 * 包括 city_name 所以不需要它。
    • P.S.尽管 city 和 city_name 的突出显示代码已编译并运行您的代码(带有上述修改),但没关系。但是,我注释掉了 .createFromAssets(我没有要加载的资产数据库)并包含一个 .allowMainThreadQueries(因此可以在主线程上进行测试)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-09
    • 1970-01-01
    • 1970-01-01
    • 2022-11-26
    相关资源
    最近更新 更多