createFromFile( ) doesn't populate database - Room - android-room

EDIT: 09/30
Thanks to #shb, I've determined that createFromFile() is not properly populating the database as intended.
If anyone can point out what I'm doing wrong/missing, that would be great! Please refer to the below picture for reference on the database structure. Thanks in advance!
MainActivity
class MainActivity: AppCompatActivity() {
lateinit var mDatabase: AppDatabase
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mDatabase = AppDatabase.getInstance(this)
}
fun getFacilityNbr() {
val planInfo = mDatabase.getPlanInfoDao().getPlanInfo()
Log.d("MainActivity", "Size = ${planInfo.size}") // 0
}
}
AppDatabase
#Database(entities = [PlanInfo::class], version = 1)
abstract class AppDatabase: RoomDatabase() {
companion object {
#Volatile private var instance: AppDatabase? = null
val file = File("${Environment.getExternalStorageDirectory()}/appdata/pla/assignment-file/test.db")
Log.d("App Database", "Is valid file? - ${file.isFile}") // true
fun getInstance(context Context) = instance ?: synchronized(this) {
instance ?: Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "MyDb")
.createFromFile(File("path/to/my/test.db"))
.allowMainThreadQueries()
.build().also { instance = it }
}
abstract fun getPlanInfoDao(): PlanInfoDao
}
PlanInfoDao
#Dao
interface PlanInfoDao {
#Query("SELECT * FROM PlanInfo")
fun getPlanInfo(): List<PlanInfo>
}
PlanInfo
#Entity
data class PlanInfo (
#PrimaryKey
#ColumnInfo(name = "Facility_Nbr")
val facilityNbr: String
)

#Query("SELECT * FROM PlanInfo")
It returns a list of PlanInfo
Change return type to List<PlanInfo>
#Query("SELECT * FROM PlanInfo")
fun getPlanInfo(): List<PlanInfo>
Or you might wanna rewrite your query like below.
#Query("SELECT * FROM PlanInfo where Lifecycle_ID= :lifeCycleId")
fun getPlanInfo(lifeCycleId: String): PlanInfo
EDIT
In doubt, you make sure that there is data in that table, try inserting a row successfully before fetching in your code. Make sure its there.

Related

Is there a way to make relation to Set<String> in Room?

Assume tables:
CREATE TABLE users (id INT, name VARCHAR(255))
CREATE TABLE user_roles (user_id INT REFERENCES users(id), role_name VARCHAR(255))
Entity in Room:
#Entity(tableName = "users")
data class User(
#PrimaryKey
val id: Int,
val roles: Set<String> // want it from role_name column
)
Second entity (if needed):
#Entity(tableName = "user_roles")
data class UserRole(
val user_id: Int,
val role_name: String
)
DAO
#Dao
interface UserDao {
#Query("SELECT * FROM users")
fun getAllUsers(): Flow<List<User>>
}
This will not compile because SQLite doesn't support collections. Is there any way to map the role_name from user_roles to roles property of User entity? Can Room do it automatically?
I don't think Room has automatic support for that requirement. Instead you could do a custom query to achieve that.
Refer to this example of how to do one, and adapt according to your requirements.
getAllUsersWithRoles returns flow of list of UserWithRoles. Query joins users and user_roles tables and maps results to UserWithRolesResult class.
fromResult takes list of UserWithRolesResult and maps it to list of UserWithRoles
#Dao
interface UserDao {
#Transaction
#Query("SELECT * FROM users")
fun getAllUsersWithRoles(): Flow<List<UserWithRoles>>
companion object {
private fun fromResult(result: List<UserWithRolesResult>) : List<UserWithRoles> {
val map = result.groupBy { it.userId }.mapValues { it.value.map { it.roleName }.toSet() }
return map.map { UserWithRoles(it.key, it.value) }
}
}
}
data class UserWithRoles(
val userId: Int,
val roles: Set<String>
)
data class UserWithRolesResult(
val userId: Int,
val roleName: String
)
This will not compile because SQLite doesn't support collections.
Room however does.
Consider the following working Demo based upon your code
note the comments and modifications
run on the main thread for brevity
the POJO UserWithRoles is the main inclusion that uses a Set, which is populated/used
:-
#Entity(tableName = "users")
data class User(
#PrimaryKey
val id: Int,
val name: String
/* roles would be effectively trying to store what is stored elsewhere */
//val roles: Set<String> // want it from role_name column !! Wrong place see UserWithRoles
)
#Entity(
tableName = "user_roles",
foreignKeys = [
ForeignKey(
entity = User::class,
parentColumns = ["id"],
childColumns = ["user_id"]
)
]
)
data class UserRole(
#PrimaryKey
/* In Room and Entity MUST have a primary key */
/* if user_id were a primary key then many UserRoles per User would not be allowed as
a primary key is implicitly UNIQUE. Hence dummy_id
*/
val dummy_id: Long?=null,
val user_id: Int,
val role_name: String
)
data class UserWithRoles(
#Embedded
val user: User,
#Relation(entity = UserRole::class, parentColumn = "id", entityColumn = "user_id" )
val roles: Set<UserRole>
)
#Dao
interface UserDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(user: User): Long
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(userRole: UserRole): Long
#Query("SELECT * FROM users")
fun getAllUsers(): /*Flow<*/List<UserWithRoles>/*>*/
}
#Database(entities = [User::class,UserRole::class], exportSchema = false, version = 1)
abstract class TheDatabase: RoomDatabase() {
abstract fun getUserDao(): UserDao
companion object {
private var instance: TheDatabase?=null
fun getInstance(context: Context): TheDatabase {
if (instance==null) {
instance=Room.databaseBuilder(context,TheDatabase::class.java,"the_database.db")
.allowMainThreadQueries() /* for brevity of demo */
.build()
}
return instance as TheDatabase
}
}
}
and some Activity Code to demonstrate:-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: UserDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getUserDao()
dao.insert(User(10,"Fred"))
dao.insert(User(11,"Mary"))
dao.insert(User(12,"Jane"))
dao.insert(UserRole(user_id = 10, role_name = "Role1"))
dao.insert(UserRole(user_id = 10, role_name = "Role2"))
dao.insert(UserRole(user_id = 10, role_name = "Role3"))
dao.insert(UserRole(user_id = 11, role_name = "Role4"))
for (uwr in dao.getAllUsers()) {
val sb = StringBuilder()
for (r in uwr.roles) {
sb.append("\n\tRole is ").append(r.role_name)
}
Log.d("DBINFO","User is ${uwr.user.name} and has ${uwr.roles.size} roles. They are ${sb}")
}
}
}
When run then the log includes:-
D/DBINFO: User is Fred and has 3 roles. They are
Role is Role3
Role is Role2
Role is Role1
D/DBINFO: User is Mary and has 1 roles. They are
Role is Role4
D/DBINFO: User is Jane and has 0 roles. They are

The columns returned by the query does not have the fields [id,password]

I have log in app with pre populated database (ROOM).
When i'm logging in I need to check email and password in database then after that if email and password are correct, show final fragment. But i have error which is higher.
I was searching but nothing helps me.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.continuebutton.setOnClickListener {
if(binding.EmailSpace.length() == 0){
Toast.makeText(context,"Поле пустое введите email",Toast.LENGTH_SHORT).show()
}
if(binding.PasswordSpace.length() == 0){
Toast.makeText(context,"Поле пустое введите пароль",Toast.LENGTH_SHORT).show()
}
if(binding.PasswordSpace.length() <8){
Toast.makeText(context,"Пароль слишком короткий",Toast.LENGTH_SHORT).show()
}
val espace = binding.EmailSpace.text.toString()
val pspace = binding.EmailSpace.text.toString()
if(espace.isNotEmpty() && pspace.isNotEmpty()){
check(espace == "admin#gmail.com")
check(pspace == "12345678")
scope.launch {
db.getDAO().findbyEmail(espace)
db.getDAO().findbypassword((pspace))
}
findNavController().navigate(R.id.action_loginFragment_to_lastfragment)
} else{
Toast.makeText(context,"Данные введены не верно",Toast.LENGTH_SHORT)
}
}
}
This is a fragment in which i'm checking edittexts for empty.
AdminDatabaseclass
import Room.DAO.DAO
import Room.Repository.Admindata
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
#Database (entities = [Admindata::class], version = 1, exportSchema = true)
abstract class AdminDatabase:RoomDatabase(){
abstract fun getDAO(): DAO
companion object{
fun getadminDB(context: Context):AdminDatabase{
return Room.databaseBuilder(context.applicationContext,AdminDatabase::class.java,"admindb").createFromAsset("SQLDB/dbforpetproject.db").build()
}
}
DAO
#Dao
interface DAO {
#Query("select email from admindb where email = :email")
suspend fun findbyEmail(email:String) : Admindata
#Query("select password from admindb where password = :password")
suspend fun findbypassword(password:String) : Admindata
}
Admindata
#Entity(tableName= "admindb")
data class Admindata(
#PrimaryKey
val id:Int = 1,
#ColumnInfo(name = "email")
val email:String = "admin#gmail.com",
#ColumnInfo(name = "password")
val password :String = "12345678"
)
Error after launching how it looks like enter image description here
Another one screenshot enter image description here
You want to use
#Dao
interface DAO {
#Query("select * from admindb where email = :email")
suspend fun findbyEmail(email:String) : Admindata
#Query("select * from admindb where password = :password")
suspend fun findbypassword(password:String) : Admindata
}
Then the Admindata returned will have all the values from the database rather than just the email (findbyEmail) or the password (findbyPassword).
The * represents all columns, which is the equivalent of SELECT id, email, password FROM admindb ....

How can I infer generic type in kotlin? (covariance problem)

I have two class which extends Data, interface.
A: Data
B: Data
Then I have two repositories. TestRepository is interface which get generic class.
TestRepository<T: Data> {
fun save(data: T): T
}
#Repository
ARepository: TestRepository<A> {
override fun save(data: A): A
}
#Repository
BRepository: TestRepository<B> {
override fun save(data: B): B
}
it all have save method which gets data from generic type, and returns generic type.
ARepo and BRepo gets data from A: Data, B:Data and returns corresponding type.
Then we have new Service,
#Service
CService(
private aRepository: ARepository,
private bRepository: BRepository
) {
fun test(t: String): TestRepository<out Data> =
when (t) {
'1' -> aRepository
'2' -> bRepository
else -> throw Error("error")
}
}
it returns aRepository or bRepository, so return type of test function is TestRepository<out Data>. But when I try to use that class with DI,
#Service
class TestClass(
private val cService: CService
) {
fun cServiceTest() {
...
val saveObject = Data('')
val repo = cService.test("1") // or "2"
repo.save(saveObject) <-- error
}
}
repo.save emits error,
Type mismatch.
Required:
Nothing
Found:
Data
How can I solve this error?
How about this?
import kotlin.reflect.KClass
interface Data {
val name: String
}
data class A(
override val name: String = "A"
) : Data
data class B(
override val name: String = "B"
) : Data
interface DataRepository<T : Data> {
fun save(data: T): T
}
class ARepository : DataRepository<A> {
override fun save(data: A): A {
println("Saved - (A)")
return data.copy()
}
}
class BRepository : DataRepository<B> {
override fun save(data: B): B {
println("Saved - (B)")
return data.copy()
}
}
class DataService(
private val aClassRepository: ARepository = ARepository(),
private val bClassRepository: BRepository = BRepository(),
) {
#Suppress("UNCHECKED_CAST")
fun <T : Data> getRepository(_class: KClass<T>): DataRepository<T> =
when (_class) {
A::class -> aClassRepository
B::class -> bClassRepository
else -> throw RuntimeException()
} as? DataRepository<T> ?: throw RuntimeException()
}
fun main() {
val service = DataService()
val repository = service.getRepository(A::class)
val saved = repository.save(A())
println(saved)
}
I don't know what kind of problem you are trying to solve.
So I think the solution I came up with is not good.

How to initialize variables in parent abstract class of spring bean using Kotlin?

I have the next structure of spring beans
abstract class GenericRepository<T> {
private val FIND_BY_ID_SQL = "SELECT * FROM ${this.getTableName()} WHERE id = ?"
abstract fun getTableName(): String
abstract fun jdbcTemplate(): JdbcTemplate
abstract fun getMapper(): RowMapper<T>
fun find(id: Long): T? {
return jdbcTemplate().queryForObject(FIND_BY_ID_SQL, arrayOf(id), getMapper())
}
}
User repository
#Repository
class UserRepository(
#Autowired
private val jdbcTemplate: JdbcTemplate
) : GenericRepository<User>() {
companion object {
private const val INSERT_SQL = "INSERT INTO \"user\"(name, age) VALUES (?,?)"
}
private class LogMapper : RowMapper<User> {
override fun mapRow(rs: ResultSet, rowNum: Int): User? {
return User(
id = rs.getLong("id"),
name = rs.getString("name"),
age = rs.getInt("operation")
)
}
}
override fun getTableName(): String {
return "user"
}
override fun jdbcTemplate(): JdbcTemplate {
return jdbcTemplate
}
override fun getMapper(): RowMapper<User> {
return LogMapper()
}
}
The problem when Spring creates proxy and creates bean of UserRepository it doesn't initialize FIND_BY_ID_SQL leaving it null.
The question: how usign abstract class make spring initialize FIND_BY_ID_SQL variable?
UPD
I used #Component instead of #Repository and the problem was solved. FIND_BY_ID_SQL is not null anymore.
You could work around the problem by making it lazy:
private val FIND_BY_ID_SQL by lazy { "SELECT * FROM ${this.getTableName()} WHERE id = ?" }
However, you should first be sure it's an actual problem (e.g. that when you call find you get an exception), because the proxy might simply delegate to a "real" UserRepository with non-null FIND_BY_ID_SQL (and jdbcTemplate etc.), depending on Spring's internal details.
In addition, you need to be careful when your superclass properties are initialized depending on subclass; I think your exact situation should work, but I'd prefer to write it as
abstract class GenericRepository<T>(val tableName: String) {
private val FIND_BY_ID_SQL = "SELECT * FROM ${tableName} WHERE id = ?"
abstract val jdbcTemplate: JdbcTemplate
abstract val mapper: RowMapper<T>
fun find(id: Long): T? {
return jdbcTemplate.queryForObject(FIND_BY_ID_SQL, arrayOf(id), mapper)
}
}
#Repository
class UserRepository(
#Autowired
override val jdbcTemplate: JdbcTemplate
) : GenericRepository<User>("user") { ... }

Blocking problem of graphql-kotlin with DataLoader&BatchLoader

I used the framework "https://github.com/ExpediaGroup/graphql-kotlin" to learn graphql programming of kotlin under springframework.
I used DataLoader&BatchLoader to resolve the 'N+1' loading problem.
When the scope of DataLoader objects is singleton, it works, but it's not my goal because temporary cache mechanism should not be bridging over different requests.
Then I changed the scope of DataLoader objects to be prototype, in all probability, the graphql query may be blocking and associated objects will not be loaded, client waits the response forever.
What's the reason and how can I resolve it?
I did it like this:
Create a simple springboot application, add maven dependency of graph-kotlin
<dependency>
<groupId>com.expediagroup</groupId>
<artifactId>graphql-kotlin-spring-server</artifactId>
<version>2.0.0.RC3</version>
</dependency>
Create two model classes(Note: Their code will be changed in the final step)
data class Department(
val id: Long,
val name: String
)
data class Employee(
val id: Long,
val name: String,
#GraphQLIgnore val departmentId: Long
)
Create two mocked repository objects
val DEPARTMENTS = listOf(
Department(1L, "Develop"),
Department(2L, "Test")
)
val EMPLOYEES = listOf(
Employee(1L, "Jim", 1L),
Employee(2L, "Kate", 1L),
Employee(3L, "Tom", 2L),
Employee(4L, "Mary", 2L)
)
#Repository
open class DepartmentRepository {
companion object {
private val LOGGER = LoggerFactory.getLogger(DepartmentRepository::class.java)
}
open fun findByName(namePattern: String?): List<Department> = //For root query
namePattern
?.takeIf { it.isNotEmpty() }
?.let { pattern ->
DEPARTMENTS.filter { it.name.contains(pattern) }
}
?: DEPARTMENTS
open fun findByIds(ids: Collection<Long>): List<Department> { // For assciation
LOGGER.info("BatchLoad departments by ids: [${ids.joinToString(", ")}]")
return DEPARTMENTS.filter { ids.contains(it.id) }
}
}
#Repository
open class EmployeeRepository {
companion object {
private val LOGGER = LoggerFactory.getLogger(EmployeeRepository::class.java)
}
open fun findByName(namePattern: String?): List<Employee> = //For root query
namePattern
?.takeIf { it.isNotEmpty() }
?.let { pattern ->
EMPLOYEES.filter { it.name.contains(pattern) }
}
?: EMPLOYEES
open fun findByDepartmentIds(departmentIds: Collection<Long>): List<Employee> { // For association
LOGGER.info("BatchLoad employees by departmentIds: [${departmentIds.joinToString(", ")}]")
return EMPLOYEES.filter { departmentIds.contains(it.departmentId) }
}
}
Create a graphql query object to export root query operations
#Service
open class OrgService(
private val departmentRepository: DepartmentRepository,
private val employeeRepository: EmployeeRepository
) : Query {
fun departments(namePattern: String?): List<Department> =
departmentRepository.findByName(namePattern)
fun employees(namePattern: String?): List<Employee> =
employeeRepository.findByName(namePattern)
}
Create an abstract class for many-to-one associated object loading
abstract class AbstractReferenceLoader<K, R: Any> (
batchLoader: (Collection<K>) -> Collection<R>,
keyGetter: (R) ->K,
optionsInInitializer: (DataLoaderOptions.() -> Unit) ? = null
): DataLoader<K, R?>(
{ keys ->
CompletableFuture.supplyAsync {
batchLoader(keys)
.associateBy(keyGetter)
.let { map ->
keys.map { map[it] }
}
}
},
optionsInInitializer?.let {
DataLoaderOptions().apply {
this.it()
}
}
)
Create an abstract class for one-to-many associated collection loading
abstract class AbstractListLoader<K, E>(
batchLoader: (Collection<K>) -> Collection<E>,
keyGetter: (E) ->K,
optionsInInitializer: (DataLoaderOptions.() -> Unit) ? = null
): DataLoader<K, List<E>>(
{ keys ->
CompletableFuture.supplyAsync {
batchLoader(keys)
.groupBy(keyGetter)
.let { map ->
keys.map { map[it] ?: emptyList() }
}
}
},
optionsInInitializer?.let {
DataLoaderOptions().apply {
this.it()
}
}
)
Create annotation to let spring manager DataLoader beans by prototype scope
#Retention(RetentionPolicy.RUNTIME)
#Target(AnnotationTarget.CLASS)
#Component
#Scope(
ConfigurableBeanFactory.SCOPE_PROTOTYPE,
proxyMode = ScopedProxyMode.NO
)
annotation class DataLoaderComponent
Create loader bean to load the parent object reference of Employee object
#DataLoaderComponent
open class DepartmentLoader(
private val departmentRepository: DepartmentRepository
): AbstractReferenceLoader<Long, Department>(
{ departmentRepository.findByIds(it) },
{ it.id },
{ setMaxBatchSize(256) }
)
Create loader bean to load the child object collection of Department object
#DataLoaderComponent
open class EmployeeListByDepartmentIdLoader(
private val employeeRepository: EmployeeRepository
): AbstractListLoader<Long, Employee>(
{ employeeRepository.findByDepartmentIds(it) },
{ it.departmentId },
{ setMaxBatchSize(16) }
)
Create an GraphQL configuration to let 'graphql-kotlin' know all the DataLoader beans
#Configuration
internal abstract class GraphQLConfig {
#Bean
open fun dataLoaderRegistryFactory(): DataLoaderRegistryFactory =
object: DataLoaderRegistryFactory {
override fun generate(): DataLoaderRegistry = dataLoaderRegistry()
}
#Bean
#Scope(
ConfigurableBeanFactory.SCOPE_PROTOTYPE,
proxyMode = ScopedProxyMode.NO
)
protected open fun dataLoaderRegistry(
loaders: List<DataLoader<*, *>>
): DataLoaderRegistry =
DataLoaderRegistry().apply {
loaders.forEach { loader ->
register(
loader::class.qualifiedName,
loader
)
}
}
#Lookup
protected abstract fun dataLoaderRegistry(): DataLoaderRegistry
}
Add tow methods into DataFetchingEnvironment to get DataLoader objects
inline fun <reified L: AbstractReferenceLoader<K, R>, K, R> DataFetchingEnvironment.getReferenceLoader(
): DataLoader<K, R?> =
this.getDataLoader<K, R?>(L::class.qualifiedName)
inline fun <reified L: AbstractListLoader<K, E>, K, E> DataFetchingEnvironment.getListLoader(
): DataLoader<K, List<E>> =
this.getDataLoader<K, List<E>>(L::class.qualifiedName)
Change the code of Department and Employee, let them support association
data class Department(
val id: Long,
val name: String
) {
suspend fun employees(env: DataFetchingEnvironment): List<Employee> =
env
.getListLoader<EmployeeListByDepartmentIdLoader, Long, Employee>()
.load(id)
.await()
}
data class Employee(
val id: Long,
val name: String,
#GraphQLIgnore val departmentId: Long
) {
suspend fun department(env: DataFetchingEnvironment): Department? =
env
.getReferenceLoader<DepartmentLoader, Long, Department>()
.load(departmentId)
.await()
}
Build & Run
Start the SpringBoot applications, open http://locathost:8080/playground.
Execute the query, the result may be success or failed!
{
employees {
id
name
department {
id
name
employees {
id
name
}
}
}
}
If it is success, the client can get the response, and the server log is
2020-03-15 22:47:26.366 INFO 35616 --- [onPool-worker-5] org.frchen.dal.DepartmentRepository : BatchLoad departments by ids: [1, 2]
2020-03-15 22:47:26.367 INFO 35616 --- [onPool-worker-5] org.frchen.dal.EmployeeRepository : BatchLoad employees by departmentIds: [1, 2]
If it is failed, the client is blocking and waits for the response forever, and the server log is
2020-03-15 22:53:43.159 INFO 35616 --- [onPool-worker-6] org.frchen.dal.DepartmentRepository : BatchLoad departments by ids: [1, 2]

Resources