Kamil Komnacki
Kamil Komnacki

Reputation: 456

How to properly get table name in DAO class using Android Room

I wonder how to avoid hard coding when using table name in Android Room query. I develop in Kotlin, but to be honest in this problem it doesn't matter is Java or Kotlin.

Let's see that simple classes:

DAO interface:

@Dao
interface UserDAO {

    @Query("SELECT * FROM USER")
    fun getAll(): List<User>
}

Entity class:

@Entity(tableName = "USER")
class User {
}

You can see that the table name "USER" is hard coded in @Query in UserDAO. How to avoid that? How to refer to @Entity parameter tableName?

I would like to have all names in one and only one place.

Upvotes: 7

Views: 12185

Answers (4)

Alexander Ivanov
Alexander Ivanov

Reputation: 140

It might be easier.

const val USER_TABLENAME = "USER"

@Entity(tableName = USER_TABLENAME)
class User {
}

And

@Dao
interface UserDAO {
    @Query("SELECT * FROM $USER_TABLENAME")
    fun getAll(): List<User>
}

Upvotes: 1

vasiliyx
vasiliyx

Reputation: 61

You could do something like this in Java:

Create TableAnnotation.java interface file

/**
 * This annotation class is needed to obtain information about the table from the class at runtime.
 * Needed passing the BaseDao needs to retrieve the table name from the parent class.
 */
@Retention(RetentionPolicy.RUNTIME)
public @interface TableAnnotation {
    String tableName();
}

In your User.java table class do this

@TableAnnotation(tableName = User.tableName)
@Entity(tableName = User.tableName)
class User {
protected static final String tableName = "USER";
}

If you want to reduce the amount of boilerplate code, you can integrate getting the tableName in a BaseDao. In your BaseDao.java you can do this

@Dao
public abstract class BaseDao<T> {

    /**
     * Get all objects from the database
     * @return the table objects for the entries
     */
    public List<T> getAllObjects() {
        SimpleSQLiteQuery query = new SimpleSQLiteQuery(
                "select * from " + getTableName() + " order by id"
        );
        return doGetAllObjectsById_(query);
    }
    @RawQuery
    protected abstract List<T> doGetAllObjectsById_(SupportSQLiteQuery query);




    /**
     * Get an object from the database by id
     * @param id
     * @return the table object for the entry
     */
    public T getObjectById(long id) {
        SimpleSQLiteQuery query = new SimpleSQLiteQuery(
                "select * from " + getTableName() + " where id = ?",
                new Object[]{id}
        );
        return doGetObjectById_(query);
    }
    @RawQuery
    protected abstract T doGetObjectById_(SupportSQLiteQuery query);




    /**
     * Get the table name of the parent class.
     * @return
     */
    private String getTableName() {

        Class clazz = (Class)
                ((ParameterizedType) getClass().getSuperclass().getGenericSuperclass())
                        .getActualTypeArguments()[0];

        TableAnnotation tableAnnotation = (TableAnnotation) clazz.getAnnotation(TableAnnotation.class);
        String tableName = tableAnnotation.tableName();
        return tableName;
    }

}

In your UserDAO.java, change the interface into public abstract class and inherit the BaseDao. All standard methods you specified in the BaseDao will be passed on.

@Dao
public abstract class UserDAO extends BaseDao<UserDAO>
{

    // Add additional methods here specific to this table.
    // ...
}

Upvotes: 2

gary0707
gary0707

Reputation: 313

Currently for both, Kotlin and Java you can rely on the-built in SQL syntax higlighting. As a result, when you type the content of a your query, Android Studio suggests already defined table names. Maybe you have turned that feature off somehow? Don't forget to inject Android Room SQL Language

All in all, if you prefer to keep hardcoded things away, why not define a companion object belonging to the Entity class? Imho, mixing Data (layer) and Activity class is not a nice concept. Data does't know about something like activity (or any UI related stuff). Having Utils sounds like having a bug for everything, not separated from concerns point of view.

Maybe the following code would meet you requirement:

@Entity(tableName = WordPl.TABLE_NAME)
data class WordPl(var id: Int,                      
                  var word: String) {

    companion object {
        const val TABLE_NAME = "word_pl"
    }

}

Then in your DAO, you can use Kotlin String Template:

@Dao
interface DictionaryDao {

    @Query("Select * from ${WordPl.TABLE_NAME}")
    fun getAllWords(): List<WordPl>

}

Upvotes: 6

Suraj Nair
Suraj Nair

Reputation: 1847

You can make a constant in any of Util classes and can refer that variable in Entity and Dao class like this:-

Suppose in MainActivity you have

    companion object {
        const val TABLE_NAME: String="Cheese"
    }

Entity class

@Entity(tableName = MainActivity.TABLE_NAME)

and Dao class will be

@Query("SELECT * FROM "+MainActivity.TABLE_NAME)

Note :- This is an usual approach to avoid hard coded naming may be you can get table name from Entity class(Needs to be explored) :) .

Upvotes: 9

Related Questions