skip to Main Content

The problem

Suppose you have multiple gardens each with a different number of plants. Your job is check every plant of each garden from time to time. Every visit you’ve to annotate some attributes of the plant, like if its well watered and its height. The app is meant to help during these visits.

My approach

I’d like to use Room Database. So I created an entity GardenVisit that have its unique id and the date of the visit. Then I’d need a GardenAnnotation entity. This entity would have a row for every plant of the garden with its id and the traits annotated on the visited day. I thought about creating an table for every GardenVisit and link them with a one-to-one relationship, but I couldn’t find a way to do this.

Why I want to create a GardenAnnotation table for every GardenVisit?

In the app you can delete a garden visit. So, when deleting it, it should delete its GardenAnnotation table as well. This seemed the easiest way to have this feature.

Conclusion

How can I create multiple tables of the same entity in Room Database and link them with another table?

If you have a better approach, I’d appreciate if you could share it. It feels weird to create a lot of tables of the same entity, actually.

2

Answers


  1. Multiple tables for the sake of splitting up what is basically the same layout (schema) probably makes little sense and will likely complicate matters.

    From your description you have some common things:-

    • Gardens.
    • Plants.
    • Visits
    • Traits.
    • Annotations (findings/traits per visit).

    I’d suggest tables accordingly.

    A Garden table that likely has but may not be limited to a human identifier of the garden (Kew, Hanging Gardens of Babylon …. ) an (as it will already exist and is efficient) identifier (id).

    A Plant table (dandelion, rose ….) with columns for id, name and perhaps other info about the plant.

    A table (not mentioned) that maps/links/associates a plant to a garden, allowing a many to many relationship (a gardens can have many plants, a plant can be used in many gardens). 2 Columns one for the map to the Garden the other to the Plant.

    A Visit table that has the date/time of the visit perhaps start/end and a map/link…. to the garden.

    A Trait table e.g. well watered, dead (if I’m tending the plant) …. Columns would be id and trait (the exact requirements)

    An Annotation table that will link to the visit (and therefore garden) and link to the plant within the garden and a link to the trait(s) to be assigned.

    So the schema could be based upon the SQLite (to demonstrate how the database/relationships work from an SQLite pov) :-

    DROP TABLE IF EXISTS annotation;
    DROP TABLE IF EXISTS trait;
    DROP TABLE IF EXISTS visit;
    DROP TABLE IF EXISTS garden_plant_map;
    DROP TABLE IF EXISTS garden;
    DROP TABLE IF EXISTS plant;
    
    CREATE TABLE IF NOT EXISTS garden (garden_id INTEGER PRIMARY KEY, garden_name TEXT UNIQUE);
    INSERT INTO garden (garden_name) 
        VALUES('Kew' /* id will be 1 */),('Hanging Gardens of Babylon' /* id will be 2 and so on (probably)*/)
    ;
    
    CREATE TABLE IF NOT EXISTS plant(plant_id INTEGER PRIMARY KEY, plant_name TEXT UNIQUE);
    INSERT INTO plant (plant_name) 
        VALUES('Rose' /* id will be 1 etc*/),('Dandelion'),('Poppy'),('Azelia'),('Oak'),('Beech')
    ;
    CREATE TABLE IF NOT EXISTS garden_plant_map (
        garden_map INTEGER,
        plant_map INTEGER,
        PRIMARY KEY (garden_map,plant_map)
        FOREIGN KEY (garden_map) REFERENCES garden(garden_id) ON DELETE CASCADE ON UPDATE CASCADE
        FOREIGN KEY (plant_map) REFERENCES plant(plant_id) ON DELETE CASCADE ON UPDATE CASCADE
    );
    INSERT INTO garden_plant_map
        VALUES
            (1 /* Kew */, 3 /* Poppy*/),
            (1 /* Kew */, 1 /* Rose */),
            (2 /* Babylon */, 2 /* Dandelion */),
            (2,5),(2,6) /*Oak and Beech for Babylon */
    ;
    CREATE TABLE IF NOT EXISTS trait (trait_id INTEGER PRIMARY KEY, trait_description UNIQUE);
    INSERT INTO trait (trait_description)
        VALUES ('Well watered'),('Dead'),('Stressed'),('Flourishing'),('under watered')
    ;
    CREATE TABLE IF NOT EXISTS visit (
        visit_id INTEGER PRIMARY KEY, 
        garden_map INTEGER,
        start_of_visit TEXT /* will be date in yyyy-mm-dd hh:mm:ss format*/,
        end_of_visit TEXT,
        visit_done INTEGER, /* 0/false or 1 (or greater)/true */
        FOREIGN KEY (garden_map) REFERENCES garden(garden_id) ON DELETE CASCADE ON UPDATE CASCADE
    );
    
    INSERT INTO visit (garden_map,start_of_visit,end_of_visit,visit_done)
        VALUES 
            (1,'2020-01-01 08:00','2021-01-01 10:00',true)
            ,(1,'2021-01-01 08:00','2021-01-01 10:00',false)
            ,(2,'2021-02-01 08:00','2021-02-01 10:00',false)
            ,(1,'2021-03-01 08:00','2021-03-01 10:00',false)
            ,(2,'2021-04-01 08:00','2021-04-01 10:00',false)
    ;
    
    CREATE TABLE IF NOT EXISTS annotation (
        annotation_id INTEGER PRIMARY KEY,
        visit_map INTEGER REFERENCES visit(visit_id) ON DELETE CASCADE ON UPDATE CASCADE,
        trait_map INTEGER REFERENCES trait(trait_id) ON DELETE CASCADE ON UPDATE CASCADE,
        garden_plant_map_garden_map INTEGER, garden_plant_map_plant_map INTEGER,
        FOREIGN KEY (garden_plant_map_garden_map,garden_plant_map_plant_map) REFERENCES garden_plant_map(garden_map,plant_map)
    );
    
    INSERT INTO annotation (visit_map, trait_map, garden_plant_map_garden_map, garden_plant_map_plant_map ) 
        VALUES
            (1 /* visit on 1/1/20 */, 1 /* Well watered */, 1 /* Kew */, 3 /* Poppy */ )
            , (1 /* visit on 1/1/20 */, 5 /* under watered */, 1 /* Kew */, 1 /* Rose */ )
            
            , (3 /* visit on 1/2/21 */, 2 /* dead */, 2 /* Babylon */, 2 /* Dandelion */ )
            , (3 /* visit on 1/2/21 */, 4 /* flourishing */, 2 /* babylon */, 6 /* Beech */ )
            , (3 /* visit on 1/2/21 */, 3 /* stressed */, 2 /* babylon */, 5 /* Beech */ )      
    ;
    
    SELECT 
        garden_name,
        start_of_visit,end_of_visit, visit_done,
        plant.plant_name,
        trait.trait_description,
            CASE WHEN visit_done THEN 'Completed' ELSE 'ToDO' END AS status
    FROM annotation
    JOIN visit ON visit.visit_id = annotation.visit_map
    JOIN garden ON visit.garden_map = garden.garden_id
    JOIN plant ON garden_plant_map_plant_map = plant_id
    JOIN trait ON trait_map = trait_id
    ;
    

    The result of the query being :-

    enter image description here

    And Lets say the visit with an id of 1 is deleted (although you could perhaps consider the visit_done being true as effectively deleting (so you could always go back in time)) e.g. using :-

    DELETE FROM visit WHERE visit_id = 3;
    

    Then the same query returns :-

    enter image description here

    i.e. the three annotations for visit 3 have been removed

    Ignoring the deletion i.e. with the visit with a visit_id of 3 remaining then the tables look like :-

    garden

    enter image description here

    plant

    enter image description here

    trait

    enter image description here

    visit

    enter image description here

    garden_plant_map

    enter image description here

    annotation

    enter image description here

    Login or Signup to reply.
  2. Following on

    The Entities. in Kotlin, from the above (work in progress/untested) :-

    Garden

    /* CREATE TABLE IF NOT EXISTS garden (garden_id INTEGER PRIMARY KEY, garden_name TEXT UNIQUE);*/
    @Entity( indices = [Index(value = ["garden_name"], unique = true)])
    data class Garden (
        @PrimaryKey
        @ColumnInfo(name = "garden_id")
        val id: Long? = null, /* specifying null or not supplying value results in auto-generated id with overheads of autogenerate = true */
        @ColumnInfo(name = "garden_name")
        val name: String
    )
    

    Plant

    /* CREATE TABLE IF NOT EXISTS plant(plant_id INTEGER PRIMARY KEY, plant_name TEXT UNIQUE); */
    @Entity(indices = [Index( value = ["plant_name"], unique = true)])
    data class Plant (
        @PrimaryKey
        @ColumnInfo(name = "plant_id")
        val id: Long? = null,
        @ColumnInfo(name = "plant_name")
        val name: String
    )
    

    Trait

    /* CREATE TABLE IF NOT EXISTS trait (trait_id INTEGER PRIMARY KEY, trait_description UNIQUE); */
    @Entity(indices = [Index(value = ["trait_description"], unique = true)])
    data class Trait(
        @PrimaryKey
        @ColumnInfo(name = "trait_id")
        val id: Long? = null,
        @ColumnInfo(name = "trait_description")
        val description: String
    )
    

    GardenPlantMap

    /* CREATE TABLE IF NOT EXISTS garden_plant_map (
        garden_map INTEGER,
        plant_map INTEGER,
        PRIMARY KEY (garden_map,plant_map)
        FOREIGN KEY (garden_map) REFERENCES garden(garden_id) ON DELETE CASCADE ON UPDATE CASCADE
        FOREIGN KEY (plant_map) REFERENCES plant(plant_id) ON DELETE CASCADE ON UPDATE CASCADE
       );
     */
    @Entity(
        tableName = "garden_plant_map",
        primaryKeys = ["garden_map","plant_map"],
        foreignKeys = [
            ForeignKey(
                entity =  Garden::class,
                parentColumns = ["garden_id"],
                childColumns = ["garden_map"],
                onDelete = CASCADE,
                onUpdate = CASCADE
            ),
            ForeignKey(
                entity = Plant::class,
                parentColumns = ["plant_id"],
                childColumns = ["plant_map"],
                onDelete = CASCADE,
                onUpdate = CASCADE
            )
        ]
    )
    data class GardenPlantMap(
        val garden_map: Long,
        @ColumnInfo(index = true) /* indexed as will likely map via column */
        val plant_map: Long
    )
    

    Visit

    /*
        CREATE TABLE IF NOT EXISTS visit (
            visit_id INTEGER PRIMARY KEY,
            garden_map INTEGER,
            start_of_visit TEXT /* will be date in yyyy-mm-dd hh:mm:ss format*/,
            end_of_visit TEXT,
            visit_done INTEGER, /* 0/false or 1 (or greater)/true */
            FOREIGN KEY (garden_map) REFERENCES garden(garden_id) ON DELETE CASCADE ON UPDATE CASCADE
        );
     */
    @Entity(
        foreignKeys = [
                ForeignKey(
                    entity = Garden::class,
                    parentColumns = ["garden_id"],
                    childColumns = ["garden_map"],
                    onDelete = CASCADE,
                    onUpdate = CASCADE
                )
        ]
    )
    data class Visit(
        @PrimaryKey
        @ColumnInfo(name = "visit_id")
        val id: Long? = null,
        @ColumnInfo(index = true)
        val garden_map: Long,
        @ColumnInfo(name = "start_of_visit")
        val visitStart: String, /* could be Long */
        @ColumnInfo(name = "end_of_visit")
        val visitEnd: String,
        @ColumnInfo(name = "visit_done")
        val visitDone: Boolean
    )
    

    Annotation

    /*
        CREATE TABLE IF NOT EXISTS annotation (
            annotation_id INTEGER PRIMARY KEY,
            visit_map INTEGER REFERENCES visit(visit_id) ON DELETE CASCADE ON UPDATE CASCADE,
            trait_map INTEGER REFERENCES trait(trait_id) ON DELETE CASCADE ON UPDATE CASCADE,
            garden_plant_map_garden_map INTEGER, garden_plant_map_plant_map INTEGER,
            FOREIGN KEY (garden_plant_map_garden_map,garden_plant_map_plant_map) REFERENCES garden_plant_map(garden_map,plant_map)
        );
     */
    @Entity(
        indices = [
            Index(value = ["garden_plant_map_garden_map","garden_plant_map_plant_map"])
    
                  ],
        foreignKeys = [
            ForeignKey(
                entity = Visit::class,
                parentColumns = ["visit_id"],
                childColumns = ["visit_map"],
                onDelete = CASCADE,
                onUpdate = CASCADE
            ),
            ForeignKey(
                entity = Trait::class,
                parentColumns = ["trait_id"],
                childColumns = ["trait_map"],
                onDelete = CASCADE,
                onUpdate = CASCADE
            ),
            ForeignKey(
                entity = GardenPlantMap::class,
                parentColumns = ["garden_map","plant_map"],
                childColumns = ["garden_plant_map_garden_map","garden_plant_map_plant_map"] /*,
                onDelete = CASCADE,
                onUpdate = CASCADE
                */
            )
    
        ]
    )
    data class Annotation(
        @PrimaryKey
        @ColumnInfo(name = "annotation_id")
        val id: Long? = null,
        @ColumnInfo(name = "visit_map", index = true)
        val visitMap: Long,
        @ColumnInfo(name = "trait_map", index = true)
        val traitMap: Long,
        @ColumnInfo( name ="garden_plant_map_garden_map")
        val gardenPlantMap_garden_map: Long,
        @ColumnInfo( name ="garden_plant_map_plant_map")
        val gardenPlantMap_plant_map: Long
    )
    

    Two POJOs (alternatives) for getting the final query, which has the additional computed/derived column status Example1POJO and Example2POJO :-

    data class Example1POJO (
        @Embedded
        val garden: Garden,
        @Embedded
        val visit: Visit,
        @Embedded
        val plant: Plant,
        @Embedded
        val trait: Trait,
        val status: String
    )
    

    and

    data class Example2POJO(
        val garden_id: Long,
        val garden_name: String,
        val visit_id: Long,
        val visit_done: Boolean,
        val start_of_visit: String,
        val end_of_visit: String,
        val plant_id: Long,
        val plant_name: String,
        val trait_id: Long,
        val trait_description: String,
        val status: String
    )
    

    A single Dao abstract class (could be interface) GardenVisitDao

    @Dao
    abstract class GardenVisitDao {
    
        @Insert(onConflict = OnConflictStrategy.IGNORE)
        abstract fun insert(garden: Garden): Long
        @Insert(onConflict = OnConflictStrategy.IGNORE)
        abstract fun insert(plant: Plant): Long
        @Insert(onConflict = OnConflictStrategy.IGNORE)
        abstract fun insert(trait: Trait): Long
        @Insert(onConflict = OnConflictStrategy.IGNORE)
        abstract fun insert(gardenPlantMap: GardenPlantMap): Long /* not really of use */
        @Insert(onConflict = OnConflictStrategy.IGNORE)
        abstract fun insert(visit: Visit): Long
        @Insert(onConflict = OnConflictStrategy.IGNORE)
        abstract fun insert(annotation: Annotation): Long
    
        @Query("SELECT * FROM garden")
        abstract fun getAllFromGarden(): List<Garden>
        @Query("SELECT garden_id FROM garden WHERE garden_name=:gardenName")
        abstract fun getGardenIdByGardenName(gardenName: String): Long
        @Query("SELECT plant_id FROM plant WHERE plant_name=:plantName")
        abstract fun getPlantIdByPlantName(plantName: String): Long
        @Query("SELECT trait_id FROM trait WHERE trait_description=:traitDescription")
        abstract fun getTraitIdByDescription(traitDescription: String): Long
        @Query("SELECT visit_id FROM visit WHERE start_of_visit=:visitStartDateTime")
        abstract fun getVisitIdByStartDateTime(visitStartDateTime: String): Long
    
        /*
            SELECT
            garden_name,
            start_of_visit,end_of_visit, visit_done,
            plant.plant_name,
            trait.trait_description,
                CASE WHEN visit_done THEN 'Completed' ELSE 'ToDO' END AS status
            FROM annotation
            JOIN visit ON visit.visit_id = annotation.visit_map
            JOIN garden ON visit.garden_map = garden.garden_id
            JOIN plant ON garden_plant_map_plant_map = plant_id
            JOIN trait ON trait_map = trait_id
            ;
         */
    
        @Query("SELECT " +
                "garden.*," +
                "visit.*," +
                "plant.*," +
                "trait.*," +
                "CASE WHEN visit_done THEN 'Completed' ELSE 'ToDO' END AS status" +
                " FROM annotation " +
                "JOIN visit ON visit.visit_id = annotation.visit_map " +
                "JOIN garden ON visit.garden_map = garden.garden_id " +
                "JOIN plant ON annotation.garden_plant_map_plant_map = plant.plant_id " +
                "JOIN trait ON trait.trait_id = annotation.trait_map")
        abstract fun getAllAnnotationsOverviewV1(): List<Example1POJO>
    
    
        @Query("SELECT " +
                "garden.*," +
                "visit.*," +
                "plant.*," +
                "trait.*," +
                "CASE WHEN visit_done THEN 'Completed' ELSE 'ToDO' END AS status" +
                " FROM annotation " +
                "JOIN visit ON visit.visit_id = annotation.visit_map " +
                "JOIN garden ON visit.garden_map = garden.garden_id " +
                "JOIN plant ON annotation.garden_plant_map_plant_map = plant.plant_id " +
                "JOIN trait ON trait.trait_id = annotation.trait_map")
        abstract fun getAllAnnotationsOverviewV2(): List<Example2POJO>
    }
    

    An @Database class GardenVisitDatabase

    @Database(entities = [
        Garden::class,
        Plant::class,
        Trait::class,
        Visit::class,
        Annotation::class,
        GardenPlantMap::class],
        version = 1
    )
    abstract class GardenVisitDatabase: RoomDatabase() {
        abstract fun getGardenVisitDao(): GardenVisitDao
        companion object {
            @Volatile
            private var instance: GardenVisitDatabase? = null
            fun getInstance(context: Context): GardenVisitDatabase {
                if (instance == null) {
                    instance = Room.databaseBuilder(
                        context,GardenVisitDatabase::class.java,
                        "gardenvisit.db"
                    )
                        .allowMainThreadQueries()
                        .build()
                }
                return instance as GardenVisitDatabase
            }
        }
    }
    
    • note allowMainThreadQueries allows everything (most) to be run on the main thread (good for testing).

    Finally loading and extracting data from an Activity that replicates (closely) the SQL in the answer (two versions of the last query).

    class MainActivity : AppCompatActivity() {
        lateinit var db: GardenVisitDatabase
        lateinit var dao: GardenVisitDao
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            db = GardenVisitDatabase.getInstance(this)
            dao = db.getGardenVisitDao()
    
            /*
            INSERT INTO garden (garden_name)
                VALUES('Kew' /* id will be 1 */),('Hanging Gardens of Babylon' /* id will be 2 and so on (probably)*/)
            ;
             */
            val keygardens_id = dao.insert(Garden(name = "Kew"))
            val babylon_id = dao.insert(Garden(name = "hanging Gardens of Babylon"))
            /*
            INSERT INTO plant (plant_name)
                VALUES('Rose' /* id will be 1 etc*/),('Dandelion'),('Poppy'),('Azelia'),('Oak'),('Beech')
            ;
             */
            val rose_id = dao.insert(Plant(name = "Rose"))
            val dand_id = dao.insert(Plant(name = "Dandelion"))
            val popp_id = dao.insert(Plant(name = "Poppy"))
            val azel_id = dao.insert(Plant(name = "Azelia"))
            val oak_id = dao.insert(Plant(name = "Oak"))
    
            val ww_id = dao.insert(Trait(description = "Well Watered"))
            val dead_id = dao.insert(Trait(description = "Dead"))
            val uw_id = dao.insert(Trait(description = "Under Watered"))
            val str_id = dao.insert(Trait(description = "Stressed"))
            val flour_id = dao.insert(Trait(description = "Flourishing"))
    
            dao.insert(GardenPlantMap( dao.getGardenIdByGardenName("Kew"),dao.getPlantIdByPlantName("Rose")));
            dao.insert(GardenPlantMap(keygardens_id,popp_id))
            dao.insert(GardenPlantMap(babylon_id,dand_id))
            dao.insert(GardenPlantMap(babylon_id,oak_id))
            dao.insert(GardenPlantMap(babylon_id,dao.insert(Plant(name = "Beech"))))
    
            val vkew20210101 = dao.insert(Visit(garden_map = keygardens_id, visitStart = "2020-01-01 08:00", visitEnd = "2021-01-01 10:00", visitDone = true))
            val vkew20220101 = dao.insert(Visit(garden_map = keygardens_id, visitStart = "2022-01-01 08:00", visitEnd = "2022-01-01 10:00", visitDone = false))
            val vbab20220201 = dao.insert(Visit(garden_map = babylon_id, visitStart = "2022-02-01 08:00", visitEnd = "2022-02-01 10:00", visitDone = false))
            val vkey20220301 = dao.insert(Visit(garden_map = keygardens_id, visitStart = "2022-03-01 08:00", visitEnd = "2022-03-01 10:00", visitDone = false))
            val vbab20220401 = dao.insert(Visit(garden_map = babylon_id, visitStart = "2022-04-01 08:00", visitEnd = "2022-04-01 10:00", visitDone = false))
            val vkey20220501 = dao.insert(Visit(garden_map = keygardens_id, visitStart = "2022-05-01 08:00", visitEnd = "2022-05-01 10:00", visitDone = false))
    
            dao.insert(Annotation(visitMap = vkew20210101, traitMap =  ww_id, gardenPlantMap_garden_map = keygardens_id, gardenPlantMap_plant_map = popp_id))
            dao.insert(Annotation(visitMap = vkew20210101, traitMap = uw_id, gardenPlantMap_garden_map = keygardens_id, gardenPlantMap_plant_map = rose_id))
            dao.insert(Annotation(visitMap = vbab20220201, traitMap = dead_id, gardenPlantMap_garden_map = babylon_id, gardenPlantMap_plant_map = dand_id))
            dao.insert(Annotation(visitMap = vbab20220201, traitMap = flour_id, gardenPlantMap_garden_map = babylon_id, gardenPlantMap_plant_map = dao.getPlantIdByPlantName("Beech")))
            dao.insert(Annotation(visitMap = vbab20220201, traitMap = str_id, gardenPlantMap_garden_map = babylon_id, gardenPlantMap_plant_map = oak_id))
    
            for(ex1: Example1POJO in dao.getAllAnnotationsOverviewV1()) {
                Log.d("GARDENDBINFO","Garden is ${ex1.garden.name} starts: ${ex1.visit.visitStart} ends: ${ex1.visit.visitEnd}. Plant is ${ex1.plant.name}. Trait is ${ex1.trait.description}. Status is ${ex1.status} ")
            }
            for(ex2: Example2POJO in dao.getAllAnnotationsOverviewV2()) {
                Log.d("GARDENDBINFO","Garden is ${ex2.garden_name} starts: ${ex2.start_of_visit} ends: ${ex2.end_of_visit}. Plant is ${ex2.plant_name}. Trait is ${ex2.trait_description}. Status is ${ex2.status} ")
    
            }
        }
    }
    

    Result output to the log

    40.959D/GARDENDBINFO: Garden is Kew starts: 2020-01-01 08:00 ends: 2021-01-01 10:00. Plant is Poppy. Trait is Well Watered. Status is Completed 
    40.959D/GARDENDBINFO: Garden is Kew starts: 2020-01-01 08:00 ends: 2021-01-01 10:00. Plant is Rose. Trait is Under Watered. Status is Completed 
    40.960D/GARDENDBINFO: Garden is hanging Gardens of Babylon starts: 2022-02-01 08:00 ends: 2022-02-01 10:00. Plant is Dandelion. Trait is Dead. Status is ToDO 
    40.960D/GARDENDBINFO: Garden is hanging Gardens of Babylon starts: 2022-02-01 08:00 ends: 2022-02-01 10:00. Plant is Beech. Trait is Flourishing. Status is ToDO 
    40.960D/GARDENDBINFO: Garden is hanging Gardens of Babylon starts: 2022-02-01 08:00 ends: 2022-02-01 10:00. Plant is Oak. Trait is Stressed. Status is ToDO
    
     
    40.962D/GARDENDBINFO: Garden is Kew starts: 2020-01-01 08:00 ends: 2021-01-01 10:00. Plant is Poppy. Trait is Well Watered. Status is Completed 
    40.962D/GARDENDBINFO: Garden is Kew starts: 2020-01-01 08:00 ends: 2021-01-01 10:00. Plant is Rose. Trait is Under Watered. Status is Completed 
    40.962D/GARDENDBINFO: Garden is hanging Gardens of Babylon starts: 2022-02-01 08:00 ends: 2022-02-01 10:00. Plant is Dandelion. Trait is Dead. Status is ToDO 
    40.962D/GARDENDBINFO: Garden is hanging Gardens of Babylon starts: 2022-02-01 08:00 ends: 2022-02-01 10:00. Plant is Beech. Trait is Flourishing. Status is ToDO 
    40.962D/GARDENDBINFO: Garden is hanging Gardens of Babylon starts: 2022-02-01 08:00 ends: 2022-02-01 10:00. Plant is Oak. Trait is Stressed. Status is ToDO 
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search