📜 ⬆️ ⬇️

Writing Your Orm for Android with Canas and Senorites, Part 3

Introduction


After a break in the development of my Android application , during which more and more new ideas were formed in my head, how to make it more beautiful and comfortable, at the end of January I sat down again for development. During my thinking, the approach to creating an application was slightly transformed, and therefore I only got to the object model three weeks ago. And almost immediately faced with the need for refinement UCAOrm . Who is interested in learning not only about the innovations already implemented, but also about the fact that only during the development process -

Changes and additions


The first thing I encountered was the need for ContentProvider and Cursor .
There are no particular problems with ContentProvider - the abstract OrmContentProvider is inherited from ContentProvider and it still implements two methods: query , which accepts OrmWhere and returns OrmCursor , and update , which accepts the instance being updated. OrmCursor is inherited from AbstractCursor and, besides the implementation of all the necessary methods, it also implements the getEntities method, which returns a List of objects. The most interesting, from the point of view of implementation, are the getColumnNames function, which returns an array of column names ( has already remade the getOrmFields function), and the private function getObject , which returns the value of the specified column. These classes have greatly simplified the development of account synchronization.
The second innovation was the support of new field types: boolean and int array . If everything is more or less clear with boolean , I’ll tell you about the array in more detail. First, the idea was to create an additional table with the name “ class name_ field name ” and one single column of the type of the array component. However, after speculating, I came to the conclusion that an array with a class inherited from OrmEntity destroys the entire architecture, and any other non-primitive type the developer still has to be serialized manually. From this, I decided that orm will only support arrays of primitive types that are perfectly serialized into a string and also perfectly deserialized back. Problems, perhaps, can arise with double , the format of which in the form of a string can contain a comma, which is the separator of the elements of the array, but they are easily solved by hard setting the locale in English .
Also, I finally got to the implementation of the getDefaultValues method in OrmHelper 's successor. Now it looks like this:
@Override public void getDefaultValues(Class<? extends OrmEntity> entityClass, List<OrmEntity> valueList) { } 

accordingly, adding default values ​​for our favorite model from the second part will be implemented like this:
  public void getDefaultValues(Class<? extends OrmEntity> entityClass, List<OrmEntity> valueList) { if (entityClass.equals(CarType.class)) { valueList.add(new CarType("Passenger")); valueList.add(new CarType("Truck")); } } 

Well, now we have gotten close to the most delicious problem that hardex spoke about in the first article - updating the data schema.

Updating data schema


Again, back to our model and consider the essence of Car :
  @Table(rightJoinTo = {Truck.class}) public class Car extends BaseEntity { @Column(name = "car_type") private CarType type; @Column private List<Wheel> wheels; @Column(name = "engine_power") private int enginePower; @Column(name = "doors_count") private int doorsCount; } 

Suppose we decide to add another field:
  @Column(name = "max_speed") private int maxSpeed; 

In this case, we need to change the version of the database in the manifest:
 <meta-data android:name="UO_DB_VERSION" android:value="2" /> 

And write the code in the onUpdate helper method:
  @Override protected void onUpgrade(int oldVersion, int newVersion) { if (newVersion == 2) { OrmUtils.UpdateTable(Car.class).work(); } } 

“ Why else do we need a work method? ”- someone will ask. And let's consider possible options for changing the data schema:
  1. A new field is added to the schema.
  2. The field is removed from the schema.
  3. The field is renamed.
  4. The field changes type.

Most likely, many have already guessed that the only item that does not cause difficulties is the first one, but we will consider them in order.
')
Field additions

Everything is easy here: orm scoops the fields from the table and compares it with the fields from the class. When a new field is found in the object model, it jerks for it
 ALTER TABLE … ADD COLUMN … 
If you need a default value, you will need to specify it in the annotation.

Deleting a field

The beginning of the algorithm is similar to the previous one: we compare the fields and find those that need to be deleted. Well, then, almost, as indicated in faq'e . The only thing I don’t understand is why the second copy is needed, because after the drop 'zeros of the table, you can just rename the temporary one and it will become permanent!

Rename field

And here work is not your assistant! Orm simply will not understand that you just renamed the field and will do two things: remove the field with the old name from the base and add a new one with the new one. Of course, it was also possible to beat this in the annotation by adding the old_name field, but it seemed to me that this was too much, and the orm could be unloaded, telling him exactly what to do. In light of the foregoing in this paragraph, we need the rename method:
 OrmUtils.UpdateTable(Car.class).renameColumn("old_column_name", "new_column_name"); 

Note that you need to specify the name of the column, not the field! As a result, orm will not wool the entire class and all fields in the database in order to understand what it needs to change, but simply make changes to the name of one single column.
Also, we can help him add a column:
 OrmUtils.UpdateTable(Car.class).addColumn("column_name"); 

and delete the column:
 OrmUtils.UpdateTable(Car.class).deleteColumn(“column_name”); 

The very same renaming in the form of a sql query caused some questions. At first, I decided this, as well as the deletion, by creating a new table with the desired field name, where the data is copied from the old one, and it is simply deleted, and the new one is renamed. But then, I stumbled upon this article and plan to try this method.

Change field type

Orm, again, can do everything for you, but you can help him:
 OrmUtils.UpdateTable(Car.class).changeColumnType("column_name"); 

In principle, the second parameter could also be passed on a new column type, but I did not want to give the programmer the opportunity to specify the wrong type, and then scold orm (:-)). The problem of incompatibility of data of the old type and the new one is solved by the base itself, throwing an exception when copying the old table into a new one. But the column can be simply zeroed by passing true as the second parameter:
 OrmUtils.UpdateTable(Car.class).changeColumnType(“column_name”, true); 

There was also a parameter in the design that indicates that the field should be reset only in case of incompatibility of types, but so far did not become.

Conclusion


UCAOrm has undergone such changes over the past two weeks. Not everything has been laid out in github , since, as I wrote a little higher, the work on Updater is still underway, and not everything has been tested yet. There is also an idea to slightly simplify the initial creation of the tables: simply by calling OrmUtils’s createByPackeg method, passing the package name there, in which orm will look for marked classes. But this is only an idea.
As always I will be glad to any new ideas and suggestions. Wait for updates soon.

Source: https://habr.com/ru/post/216419/


All Articles