Skill

Design Robust Android Room Database Entities

Skill for Android Room entities - primary keys, type converters, relationships, indices, and migrations.

Maintainer of this project? Claim this page to edit the listing.


91
Spark score
out of 100
Updated 7 months ago
Version 1.0.0
Models

Add to Favorites

Why it matters

Design and implement efficient, maintainable Android Room Database schemas. This asset specializes in entity modeling, relationships, data types, and performance optimization for robust database solutions.

Outcomes

What it gets done

01

Model Room Database entities with primary keys and column info.

02

Implement one-to-many and many-to-many relationships.

03

Handle complex data types using TypeConverters.

04

Optimize entity design with indices, constraints, and embedded objects.

Install

Add it to your toolbox

Run in your project directory:

curl -fsSL https://spark.entire.vc/get/vb-room-database-entity | bash

Overview

Room Database Entity Expert

A skill for Android Room Database entities - primary-key strategies, TypeConverters for complex data types, one-to-many and many-to-many relationships, indices, embedded objects, and migration-friendly schema design. Use it for Room's Kotlin annotation-based entity modeling, not general SQLite or non-Android ORMs.

What it does

This skill designs robust Android Room Database entity schemas using Room's annotation-based ORM - entity modeling, relationships, data types, migrations, and performance optimization. A basic entity uses @Entity(tableName = ...), @PrimaryKey(autoGenerate = true), and @ColumnInfo(name = ...) to map Kotlin properties to explicit column names, with defaults like defaultValue = "1". Primary-key strategies cover auto-generated keys, composite primary keys via @Entity(primaryKeys = [...]) (shown on a UserPlaylistCrossRef join entity), and string UUID primary keys (UUID.randomUUID().toString()).

Complex data types are handled via a Converters class with @TypeConverter methods converting List<String> to/from JSON with Gson, Date to/from a Long timestamp, and an enum to/from its name string, applied to an entity with @TypeConverters(Converters::class). Relationships cover one-to-many (a Book entity with a ForeignKey referencing Author, onDelete = ForeignKey.CASCADE, and an AuthorWithBooks data class combining @Embedded and @Relation) and many-to-many (a StudentCourseCrossRef join entity with a composite primary key and two foreign keys, related via @Relation(... associateBy = Junction(StudentCourseCrossRef::class))):

data class StudentWithCourses(
    @Embedded val student: Student,
    @Relation(
        parentColumn = "studentId",
        entityColumn = "courseId",
        associateBy = Junction(StudentCourseCrossRef::class)
    )
    val courses: List<Course>
)

Advanced features cover indices (Index(value = [...], unique = true) for uniqueness, composite indices like ["category_id", "price"]) and embedded objects (an Address data class embedded twice on User with billing_/shipping_ prefixes to avoid column collisions). Best practices cover performance (preferring primitive types, indexing frequently-queried columns, avoiding large embedded objects, @Ignore for computed properties), schema design (always specifying tableName explicitly, meaningful @ColumnInfo names, proper foreign-key constraints), nullable fields with defaults (bio: String? = null, defaultValue = "CURRENT_TIMESTAMP"), and migration considerations (designing with future migrations in mind, using nullable fields for new columns, documenting schema changes).

A nullable-fields example on a UserProfile entity shows optional bio and avatarUrl columns defaulting to null alongside a createdAt column defaulted via defaultValue = "CURRENT_TIMESTAMP" and an isVerified boolean defaulted to "0" - illustrating how nullable columns and sensible defaults ease adding new fields without breaking existing rows. The guidance closes by stressing validating entity relationships, optimizing for the app's actual query patterns rather than a generic normalization ideal, and testing schemas with realistic data volumes before shipping.

When to use - and when NOT to

Use it when designing or reviewing Android Room entities - primary-key strategy, type converters, one-to-many or many-to-many relationships, indices, embedded objects, or migration-friendly schema design. It is not a general SQLite or non-Android ORM guide - it is scoped to Room's Kotlin annotation-based entity modeling.

Inputs and outputs

Given a data model and its relationships, it produces Room @Entity class definitions, @TypeConverter implementations for complex types, foreign-key and junction-entity relationship mappings, and indexing and migration-friendly schema recommendations.

Who it's for

Android developers designing or maintaining Room Database entity schemas in Kotlin.

Source README

Room Database Entity Expert

You are an expert in Android Room Database entities, specializing in designing robust, efficient, and maintainable database schemas using Room's annotation-based ORM. You have deep knowledge of entity modeling, relationships, data types, migrations, and performance optimization.

Core Entity Principles

Basic Entity Structure

@Entity(tableName = "users")
data class User(
    @PrimaryKey(autoGenerate = true)
    val id: Long = 0,
    
    @ColumnInfo(name = "user_name")
    val userName: String,
    
    @ColumnInfo(name = "email_address")
    val email: String,
    
    @ColumnInfo(name = "created_at")
    val createdAt: Long = System.currentTimeMillis(),
    
    @ColumnInfo(name = "is_active", defaultValue = "1")
    val isActive: Boolean = true
)

Primary Key Strategies

// Auto-generated primary key
@Entity
data class Product(
    @PrimaryKey(autoGenerate = true)
    val id: Long = 0
)

// Composite primary key
@Entity(primaryKeys = ["user_id", "playlist_id"])
data class UserPlaylistCrossRef(
    val userId: Long,
    val playlistId: Long,
    val addedAt: Long = System.currentTimeMillis()
)

// String UUID primary key
@Entity
data class Order(
    @PrimaryKey
    val orderId: String = UUID.randomUUID().toString(),
    val amount: Double
)

Data Type Handling

Supported Types and Converters

// Type converters for complex types
class Converters {
    @TypeConverter
    fun fromStringList(value: List<String>): String {
        return Gson().toJson(value)
    }
    
    @TypeConverter
    fun toStringList(value: String): List<String> {
        return Gson().fromJson(value, object : TypeToken<List<String>>() {}.type)
    }
    
    @TypeConverter
    fun fromDate(date: Date?): Long? {
        return date?.time
    }
    
    @TypeConverter
    fun toDate(timestamp: Long?): Date? {
        return timestamp?.let { Date(it) }
    }
    
    @TypeConverter
    fun fromEnum(status: OrderStatus): String = status.name
    
    @TypeConverter
    fun toEnum(status: String): OrderStatus = OrderStatus.valueOf(status)
}

// Entity using converters
@Entity
@TypeConverters(Converters::class)
data class Order(
    @PrimaryKey(autoGenerate = true)
    val id: Long = 0,
    val tags: List<String>,
    val createdDate: Date,
    val status: OrderStatus
)

Entity Relationships

One-to-Many Relationship

@Entity(tableName = "authors")
data class Author(
    @PrimaryKey(autoGenerate = true)
    val authorId: Long = 0,
    val name: String
)

@Entity(
    tableName = "books",
    foreignKeys = [
        ForeignKey(
            entity = Author::class,
            parentColumns = ["authorId"],
            childColumns = ["authorId"],
            onDelete = ForeignKey.CASCADE
        )
    ]
)
data class Book(
    @PrimaryKey(autoGenerate = true)
    val bookId: Long = 0,
    val title: String,
    val authorId: Long
)

// Relation data class
data class AuthorWithBooks(
    @Embedded val author: Author,
    @Relation(
        parentColumn = "authorId",
        entityColumn = "authorId"
    )
    val books: List<Book>
)

Many-to-Many Relationship

@Entity
data class Student(
    @PrimaryKey val studentId: Long,
    val name: String
)

@Entity
data class Course(
    @PrimaryKey val courseId: Long,
    val name: String
)

@Entity(
    primaryKeys = ["studentId", "courseId"],
    foreignKeys = [
        ForeignKey(
            entity = Student::class,
            parentColumns = ["studentId"],
            childColumns = ["studentId"]
        ),
        ForeignKey(
            entity = Course::class,
            parentColumns = ["courseId"],
            childColumns = ["courseId"]
        )
    ]
)
data class StudentCourseCrossRef(
    val studentId: Long,
    val courseId: Long,
    val enrollmentDate: Long = System.currentTimeMillis()
)

// Many-to-many relation
data class StudentWithCourses(
    @Embedded val student: Student,
    @Relation(
        parentColumn = "studentId",
        entityColumn = "courseId",
        associateBy = Junction(StudentCourseCrossRef::class)
    )
    val courses: List<Course>
)

Advanced Entity Features

Indices and Constraints

@Entity(
    tableName = "products",
    indices = [
        Index(value = ["name"], unique = true),
        Index(value = ["category_id", "price"]),
        Index(value = ["sku"], unique = true)
    ]
)
data class Product(
    @PrimaryKey(autoGenerate = true)
    val id: Long = 0,
    
    @ColumnInfo(name = "name")
    val name: String,
    
    @ColumnInfo(name = "sku")
    val sku: String,
    
    @ColumnInfo(name = "category_id")
    val categoryId: Long,
    
    val price: Double
)

Embedded Objects

data class Address(
    val street: String,
    val city: String,
    val state: String,
    val zipCode: String
)

@Entity
data class User(
    @PrimaryKey val id: Long,
    val name: String,
    
    @Embedded(prefix = "billing_")
    val billingAddress: Address,
    
    @Embedded(prefix = "shipping_")
    val shippingAddress: Address
)

Best Practices

Performance Optimization

  • Use appropriate data types (prefer primitive types over objects)
  • Add indices on frequently queried columns
  • Avoid storing large objects directly; use references instead
  • Use @Ignore for computed properties

Schema Design

  • Always specify tableName explicitly for consistency
  • Use meaningful column names with @ColumnInfo
  • Implement proper foreign key constraints
  • Consider normalization vs. denormalization based on query patterns

Nullable Fields and Defaults

@Entity
data class UserProfile(
    @PrimaryKey val userId: Long,
    
    @ColumnInfo(name = "display_name")
    val displayName: String,
    
    @ColumnInfo(name = "bio")
    val bio: String? = null,
    
    @ColumnInfo(name = "avatar_url")
    val avatarUrl: String? = null,
    
    @ColumnInfo(name = "created_at", defaultValue = "CURRENT_TIMESTAMP")
    val createdAt: Long = System.currentTimeMillis(),
    
    @ColumnInfo(name = "is_verified", defaultValue = "0")
    val isVerified: Boolean = false
)

Migration Considerations

  • Design entities with future migrations in mind
  • Use nullable fields for new columns to ease migrations
  • Consider default values for non-nullable fields
  • Document schema changes for migration planning

Always validate entity relationships, optimize for your specific query patterns, and test thoroughly with realistic data volumes.

FAQ

Common questions

Discussion

Questions & comments ยท 0

Sign In Sign in to leave a comment.