The ULTIMATE Guide to Room Migration in Android (Database Migration Tutorial)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey guys and welcome back to a new video in this video I will show you how you can successfully migrate your room database in Android so if you have some kind of database schema and that changes over time then users who already have an app running with that old schema on their app they of course need to kind of migrate to your new schema and room on its own usually doesn't know how to yeah just migrate the old schema to the new one and that's what I will show you in this video how you can tell room what it should actually do and I am in an empty Android Studio project here I already added the room dependencies so on the one hand kotlin capped for orientation processing and down here we have room so we have room runtime KTX we have the compiler and testing I will also show you testing here so how you can test your migrations which actually quite important and Truth which is an assertion Library I like to use for testing so in general we will structure this or I will structure this video in three major parts on the one hand I will show you room Auto migrations so how you can automatically migrate your room database that only works for specific things or specific changes then I will show you manual migrations how you can pretty much migrate the rest that doesn't work with auto migrations and in the third part I will show you how you can actually test your migrations and I want to start with just setting up a very simple table here for room and that will just be a user table so we can create a new data class called user here in our root package which will be a room entity and in here we will just have something like an email for example which we can make the primary key so that's unique and we set auto generate to false and let's also add a second field for example the username we can then add a little bit of a dow interface so just an interface in which we Define the functions we actually want to have to access our database to retrieve users to insert users all that stuff so we can make a user dial interface annotate that with oops Dao and in here we want to have a suspend function to insert a user oops like this we annotate this with insert and set the on conflict strategy to on conflict strategy replace so if we insert a new user with an email that already exists in our database we will simply replace that existing user and we want to have another suspend function called get users which will just retrieve all of our users in our database so we just get a list of user here we need to define a query so we need to tell room hey this is how you can actually retrieve all the users we simply insert our SQL statement we just simply want to select everything from our user table and that's it for our Dao for now at least and the last step we need to do to actually set up our room database before we can jump into migrations is we need a database class so in our root package we want to have our user database make that an abstract class and that will inherit from room database like this and here we can create our abstract vowel Dao which is our user Dao and the rest will be generated by room and we need to annotate this with database so in here we simply on the one hand Define the entities so all the tables we have in our database by simply having a little array and we only have one type of table here which is user so we enter our user double colon class and you can see we need to specify one more thing and here it starts to get interesting when it comes to migrations that is the version and I'll start this off with one but what what is this version actually now used for the version tells room or yeah SQL or other if there was a change in our schema so um just to give you a quick demo let's go to main activity and just create a simple instance of our room database we actually don't need compose here since we just wanna insert something into our database and read that and we can simply print that as a log statement so we can have our DB we can create that using room the dot database Builder pass in our replication context our user database and the name for our database as well which I will say users.db then we can say dot build and then we just insert some users some sample ones we can launch a 13 well actually we can first of all say 1 to 10 so simply create range to insert 10 users and we say for each we want to launch a curtain in lifecycle scope and we say DB dot Dao dot insert user and we create a user with yeah some kind of test email test at test and we enter our current index.com and also username would be yeah test dollar index for example so that way we just enter 10 sample users and yeah let's just read our users before that well let's just launch our app once to so we insert these users and then we can actually read them and make sure to also print them in our console so my app actually launched you don't see the emulator here because you don't need to see it we can now comment this out and we can simply say Okay lifecycle scope that launch and we say DB Dao get users and for each user we simply want to print them like this and if we now of course launch this then what we expect is that we see our 10 users that we actually inserted into our database which is exactly what happens here you can see our 10 users that is working perfectly fine so what is now up with that version I mentioned let's say we now go to our user table and we add a new field for example created so we just wanna at a timestamp when this user was created if we now relaunch this and take a look here then you will notice that our app actually crashes because room cannot verify the data integrity and it tells us looks like you've changed the schema but forgot to update the version number so that's exactly what we did we changed the schema we changed something in our table and we did not update the version number so we did not really tell room hey there's actually a change in schema and here is something here's a migration that you should use to actually migrate the old schema to the new one because right now if we take a look at our old schema and we search for user again then this is the current data that is in the app from our current user base but now we change the schema but how should room actually now know what it should assign for this new field that we introduced this created timestamp roon can know that of course so we need to kind of give it a rule that it knows what it should actually use for that field now when we migrate and that's where we come to all the migrations in room that's actually something that was introduced not too long ago I think with room version 2.4.0 or so which we currently we currently use 2.4.3 so really not too long ago Auto migrations basically well what the name already says they allow us to automatically migrate our stuff so we don't need to specify custom migration rules for room which is of course a lot easier but that of course does not work for any type of schema change only for a few but for example for the change we actually did that we just added a field that ideally has some kind of default value then that works so let's go through this step by step how we can now apply an auto migration to our room database so it will properly fill out this created field with some kind of default value or whatever we specify the first thing I want to do is I want to go to build a Grizzle app because in here under default config we want to add this block of code we need to specify some arguments for room which is a schema location so what room will now basically do is it will export our different schemas so when we actually keep on developing our database then roon will save Json files and each Json file corresponds to one specific database version in which the current schema of that version is just saved that's just very helpful if for example in future something breaks and you want to go back to a specific schema then this Json file will actually help you you should put that in Version Control just push it to your GitHub and then yeah you will just have references to the schemas of all your different versions and that will just also be needed if we want to migrate or Auto migrate our room database so we can click synchronize now and go back to where do we go user because what we now want to do here is we need to tell room what is Now the default value for that created field since that field of course did not exist in our old schema we now need to tell it for migrating what should it actually assign to that field by default and what we want to do is we want to add an annotation at column info and with that annotation we can specify that so first of all we need to specify the name which is simply create it again but then as a second argument we can specify the default value we actually need to specify that as a string here but we deal with the long so we can also just assign 0 here for example and we could also say that's equal to system current time millise however it will assign the zero as the default value so I don't think there's a way to make it use this one here as a default value and you can also not use kind of dynamic values here as the default one so in that case if you want to update all your users to a specific creative field then you would either need to make that a manual migration or you would need to Simply run some code to actually update your whole database with yeah the value you actually want but this way room will just go through our database and just add this created field to all of our user entries and set the value to zero that's not enough yet we also need to go to our user database first of all increase the version because we now change our schema we added the created field and we want to add another parameter here for our annotation which is all the migrations and in here we can specify a list of auto migrations how do we do this well we say Auto migration and here we now need to specify from which version we actually migrate to which so what this corresponds to here for this Auto migration so from would be 1 and 2 will be two so we migrate from 1 to 2 and yeah that's pretty much all we need to do I think at least let's now go here and launch this and let's hope that this does not crash again it does um schema one Json is required for migration was not found okay I see what the issue is so this schema 1 Json is now the file that room actually generates when we have a schema and we launched that in our app since we just added the compiler arguments here for room in our build.gradle app file it doesn't know our first schema so we probably just need to change the version back to one we can uncomment this or we can I mean comment this for now and we can also comment the new Fields here for now so that's just the state of our very first schema and then if we switch the project tab actually there should be here we have a schemas folder you see we already have a two dot Json file which is the our second schema for version two you can see here as our created field um it's not null it's an integer and all that stuff so this Json file just contains the current schema but there is the file one missing because we added the compile arguments later and it needs these two Json files to properly migrate so we should be fine and actually running our app again with our version one and hopefully it generates our schema one Json you can see here it is this one does not contain the created field with these two files it can now just guess the difference and properly migrate with migrate with that so if we now simply uncomment this again and we say that's version number two and in user we actually also uncomment this and we now relaunch this now that it has these two Json files take a look in lockhead and now you can see those were our users before migration with version one and now here those are the users with version 2 where the creative field is simply set to zero for every single user so let's actually take a look at what else we can do with other migrations right now we just added a field to a table which is pretty simple so then we just specify this Auto migration here and we're good but other migrations can actually do a little bit more for example what happens if we want to rename a field in a table let's say we decide hey this created field should actually now be called created at so we rename this yes we say do refactor and that is also a schema change of course but how does room now know which field actually was renamed because this could also potentially be a new field so for room it's not clear if we remove the old created field and introduce a new created ad field which is might have a totally different purpose or if we rename an existing field so runecant know that of course so what we need to do is we need to tell room hey we actually changed the name of that field and for that we want to go to user database we want to migrate this to version three now and we want to specify another Auto migration which can simply duplicate this this one we migrate from two to three and this time we also need to Define such a spec so an auto migration spec and we typically do this here in this user database class where we all basically the database you want to migrate here we can define a class I would call this migration two to three and this will be an auto migration spec and that's all you need to do so not all you need to do but I mean you don't need to actually implement this class but you need to add an annotation to this and that annotation will be rename column because that's what we did we renamed the column in one of our tables and that way we actually tell room hey our table name that actually changed was user our from column name so basically from what we renamed to something else was created and two column name is created at and now we can take this migration two to three class and specify it here as a spec so migration two to three double colon class that should be it let's relaunch our app take a look in lockhead and now you can see first we had our created fields and now we have all of our users with created add Fields so that is also working perfectly fine if we take a look in our file system we now also have a 3.json file which contains our new schema with our new created add field so that is how other migrations work um you will also need these annotations if for example you deleted a column so then you will need the delete column annotation if you deleted a table you need this or if you rename the table rename table then you can also do this with auto migrations but for the rest what you can do with uh with these annotations or if you just added a column for the rest you actually need manual migration so you will need to enter yeah SQL statements that tell room and your database which things actually change in which way so for example for more complex changes if some relations in your database change or so or if you added a whole new table to your database and that might have some relations or not and then all the migrations can't do that and in that case you will need manual migrations luckily you're here on my channel so I will also show you that next let's actually go to our root package and I will go back to the Android view here and then here we will create a new class which I'll call school so just a new entity for our database let's just say we saved some schools and we annotate this with ADD entity and I'll just make the primary key simply the name of the school and let's also set Auto generated actually false like this so now we added a new entity to our database we also need to add this here in our list so here we say School double colon class and now if we would actually relaunch our app it would crash again since we changed our schema but we did not change our version and if we would change our version and just add on add an auto migration then this would also not work because all the migrations don't work if we want to add a new table so what do we do now well let's actually first of all go to our Dow add some kind of function here to insert a school insert School make that an insert statement or insert annotation on conflict strategy as usual we set to replace and we want to have a function to actually get our schools suspend function get schools list of school and the query we run is Select all schools like this so now what we need to do for this manual migration is we need to go to main activity um usually of course in a normal project I would not put all this stuff in main activity this is just for demo but we can then actually put let's actually go to our user database and declare our migrations here instead just like our order migrations and then here we can say Val migration this one will be three to four and here we need to create that using an anonymous class which is equal to normal migration now and here we also need to specify the start version and the end version so this one is from three to four and in here we can actually implement the migrate function so this is now called as soon as a rule wants to migrate from version 3 to 4. and this time we get a database reference with this database reference we can run raw SQL code so we can say database dot execute SQL and here we can simply put in an SQL string that will be executed on migration and what we can do with this is we can create our table that room actually expects to to find to be able to insert schools using our dial then and how do we insert a new table we can say actually not select we can say create table if not exists the name of the table is school and in here we Define the different columns we have for the table we simply have a name column just for Simplicity here so that is name we say that's a character so I mean a string that is not null and that is our primary key that is how we tell room how our table actually looks like and of course if we want to do more complex migrations then you need to know SQL I think that should be clear here and that is uh beyond the scope of this video to teach you a whole SQL for example if you want to split up a table or so if you want to yeah if you want to have very complex or specific migrations then you can do this with a room of course using this strategy with this migration class but that's of course a little bit complex especially if you don't know SQL so that is our migration that is just that the table exists when we then run our app and yeah that's of course a condition that needs to be true if we want to insert something in that table we want to bump up our version to four and we want to go to main activity if we have these manual migrations we actually need to also add these to our database Builder so here before build we want to say add migrations and then here we put in all of our migrations all of our manual migrations that we have for our Rune database so so this one would be user database dot migration three to four and why doesn't it recognize that unresolved reference of course because it's not on the companion object so let's go here and put that in a companion object like this and then yeah now we are actually good let's get rid of our users now because we don't need this anymore or let's simply switch this to get schools and let's uncomment this and here we say insert school instead create a school object and the name is simply this so now since our current April I mean the last APK still uses the schema without schools and now our new schema user schools we should be able to not see any print on statements because we insert the schools afterwards but we should at least not get a crash so if we now run our app take a look in lock cat and search for no let's just keep the general one there are no crashes that's good if we search for school nothing in there that is normal that's UNL let's actually comment this so we don't insert them again relaunch this and now we should actually be able to see some schools and yes here are our schools so we successfully also added a new table to our room database with a manual migration that worked perfectly fine also if we take a look in our schemas folder here's our schema 4 which contains our new school table as well so that worked perfectly fine and now if you do this in a real live project then it can of course easily happen that you have some kind of mistake in your migration and that is not so cool because if a migration breaks that breaks your whole app because you you just saw how easily room can make make your app crash so what do we want to do we want to test our migrations with a normal automated Android test cases those will need to be instrumented tests so that means they need to run on an emulator because creating a room database requires the context and that only works with instrumented tests but I will show you how you can do that to actually check if your migration was successful or if it actually migrates what it should migrate and the first thing we want to do is want to go to our bullet Grill app file again and we want to add one more block of code here below our default config blog which is this one so we Define source sets and here we simply say okay for our Android test Source set which is the directory in which we put our instrumented unit tests or just instrumental tests here we want to Simply add our schemas directory to the sourcet so that this source that can access our database schemas because that's what's needed to actually test our migrations we hit synchronize now and I will switch back to Android and now we want to go to Android test where we put our Android tests with our instrumental tests and here we will put our migration test well let's say user migration test simply a class we Define our test Runner here above with run with and that is the Android J unit 4Runner we want to have a private const valve or database name testdb for example or just test doesn't matter here and then in here we want to actually test our migrations so there are actually two things we could test here on the one hand we could test a specific migration so for example from version one to two that works or if we say we simply want to run all of our migrations and just check if there are any issues if our app crashes in any of these we can also write a single test case for that and I will of course show you both of that and the first thing I want to do is we'll have a junit rule so these rules simply add new Behavior to our test cases and we can also use these here to just get access to special functionality in this case in regards to room for example and this room uh this room rule will be called Helper and it is a so-called migration test Helper and here we actually need to pass quite some arguments first of all the instrumentation we can get that with instrumentation registry that get instrumentation we need to specify the database class just as we also needed to do for our room Builder which is user database double colon class of java we need specs so those are now only our Auto migration specs so that we can also test these of course that will be a list of our migrations actually um actually not all of our other migrations but it means these custom ones we defined here but not all of these here so going back we simply say migration two to three that is our Auto migrations back and it seems like it has some issues let's get rid of that package name maybe yeah then it works and an open Factory which is simply a framework sqlite open helper Factory and then we don't get any more issues here we have our rule that's cool and we can now start to write our test case and we do this by simply annotating our function with a test and we call this migration one to two contains correct data for example so just making it clear what we test here we test the migration from version one to two and we want to verify that after that after migration it contains the correct data and testing migrations is also very SQL heavy so we don't have an auto migrations test that we just uh say hey test all of our migrations and verify the behavior without us needing to write any code that's sadly not so easy we first of all actually need to get a reference to our ADB that actually needs to be a VAR you will see why of our DB and that is equal to helper dot create database our names or DB name and the version you want to create is version one and then we can say apply because the the thing is if we actually create our database with a version that is not the latest one then dowels don't work because that was only work for yeah our latest database version but since we want to test the migration we actually need to create our database kind of manually here to get a reference to a support sqli database and we can then use it to run some queries to insert data and to yeah just make some checks or so so for example we can say execute SQL and then here we now want to insert the user so we insert into user we want to insert the values first of all well if we take a look in our schema you can see now why that's helpful or schema 1 because that's the database we currently create here we have Fields email and username so we now need to enter a user with these two Fields so first of all email test at test.com for example and the second one is the username for example Philip then we can close our DB so we now just have one entry in our table that we want to test if the migration was successful and the way we do this is We Now set DB to helper run migrations and validate then the name is our DB name again the version is now two so we want to migrate to two I want to set validate drop tables to true and now what do we actually want to verify well if we take a look at our first migration then here's our schema one and here's our schema 2. so what basically changed is that we added this created field and the default value is zero so we want to check that if we have our table or users table in our schema one and we migrate that to schema 2 that all of our users contain our created field with the default value being equal to zero that is what we want to now verify so now we can go here and we can say DB dot query that's what we use to actually get a list of users I'm going to get everything from our users table and then we can say that apply and here we get access to a so-called cursor that's used to actually kind of loop through the result or all the results that we get from this query and actually get access to the single columns so first of all I want to say assert that that comes from truth okay import that from truth I'm going to assert that move to First is true so move to First is used by this cursor and we basically move to the first entry and it returns true if there is a first entry so we of course first of all want to assert that there is an entry after migration that is the first thing we want to make sure and the second thing is that the created field of that entry actually exists and is equal to zero so we assert that get long because that field we want to get is along and here we need to pass an INT which is the column index we can get that using get column index of a string and the string is simply the name of the field so created and then we want to make sure that this is equal to zero because that's what we expect should happen after migration and if we now run this we hopefully see a passing test you can see it is compiling and you can see the test case actually succeeded which is exactly what we expect cool so that migration test worked so now you know how you can test the migration from one version to another one but as I said I will also show you how I can test all migrations one after another in another test and that's also not super difficult that's actually even a little bit easier than the one we wrote want to have another test function test all migrations and here we say helper.create database again with our DB name version one since we now want to migrate from version 1 to 2 to 3 to 4 and we say apply close so we simply directly close our DB we just want to create it we don't want to insert anything because all we now want to test is that nothing crashes in the end so yeah no migration will actually crash on the user side which is of course what we want to avoid and we can then say room database Builder for the context we can say instrumentation registry get instrumentation dot Target context class is user database double colon class at Java the name is DB name again and here we again say add migrations just as we did in the main activity and we need to add our migrations that are not automatic so no Auto migrations just the manual ones which is user database migration three to four and we can then say build apply and we also want to close this DB so we can say open helper writeable database that's the instance and we call close on that if we now run this test case we should hopefully also see a passing test taking a look at the results tab here and you can see here's our passing test so there are no crashes or so because if it would crash then the test case will also obviously fail so if there's any exception that is thrown by room so I hope this rather long video actually gave you a good overview of room migrations and you're actually now confident enough to also apply this in your own projects if so I would be very happy to hear about that in the comments also if you have any specific wishes or ideas for future videos I'm always happy to hear these so I get some inspiration and maybe I'll also make a video about these then if you haven't subscribed to this channel yet and you like this video and you want to see future videos that are similar like this one then now is a very good chance to actually do that it's free and maybe we'll even reach the 100 000 subscribers at the end of this year that would be amazing and yeah would be a great way if you actually want to give a little bit back for all these free videos thanks so much for the support and I will see you back in the next video have an amazing week bye
Info
Channel: Philipp Lackner
Views: 23,689
Rating: undefined out of 5
Keywords:
Id: hrJZIF7qSSw
Channel Id: undefined
Length: 33min 5sec (1985 seconds)
Published: Wed Sep 14 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.