Part 3: Using Python introspection to generate Flutter classes from Django (with rest-framework): The Flutter classes

In the previous post, I created the Django classes that form the basis of my example application. In this post I will look at the Flutter classes that I want that will reflect the Django classes. On the Flutter side, there will be one class for each Model, which translates to two classes: StatModel and StatTypeModel. For the Flutter classes, I’ll take the Django model name and append the name “Model”.

To create the code generator, we are going to create a multi-line text string in Python that contains the template of the Flutter class that we want to generate. The template will have placeholders for the various elements of the class. To create this template, I prefer to work in reverse: Create a working Flutter class sample, then remove the parts to get the template.

The Django model for Stat is the following code:

class Stat(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    type = models.ForeignKey('StatType', on_delete=models.CASCADE)
    value = models.DecimalField(max_digits=19, decimal_places=10)
    created_on = models.DateTimeField(default=timezone.now, editable=False)
    modified_on = models.DateTimeField(default=timezone.now, editable=False)

Would be represented by the following Flutter code:

class StatModel {
  final int user;
  final int type;
  double value;
  final DateTime createdOn;
  final DateTime modifiedOn;

  StatModel({
    required this.user,
    required this.type,
    required this.value,
    required this.createdOn,
    required this.modifiedOn,
  });

  factory StatModel.fromJson(Map<String, dynamic> json) {
    return StatModel(
      user: json['user'],
      type: json['type'],
      value: json['value'],
      createdOn: json['created_on'],
      modifiedOn: json['modified_on'],
    );
  }

}

Flutter/Dart uses camel case for names while Python uses a variation but snake case for variable names. In many cases, the names are the same where a property is a single word, but with more than one word, it isn’t a straight mapping: created_on becomes createdOn.

Template variables in Python are represented by curly braces with the template variable inside. The variable “{class_name}” becomes the place holder for the class name of the Flutter object. In this case, “StatModel” is the class name we would substitute into the template. What is important to notice is that curly braces are also used by Flutter/Dart for begin and end syntax to enclose statements. To make sure that our template produces the proper Flutter code we need to first double up our Flutter curly braces so that we don’t lose them when we do parameter substitution in our template. This causes our template to look like this:

template = """
class StatModel {{
  final int user;
  final int type;
  double value;
  final DateTime createdOn;
  final DateTime modifiedOn;

  StatModel({{
    required this.user,
    required this.type,
    required this.value,
    required this.createdOn,
    required this.modifiedOn,
  }});

  factory StatModel.fromJson(Map<String, dynamic> json) {{
    return StatModel(
      user: json['user'],
      type: json['type'],
      value: json['value'],
      createdOn: json['created_on'],
      modifiedOn: json['modified_on'],
    );
  }}

}}
"""

Next, I replace “StatModel” with the {class_name}:

template = """
class {class_name} {{
  final int user;
  final int type;
  double value;
  final DateTime createdOn;
  final DateTime modifiedOn;

  {class_name}({{
    required this.user,
    required this.type,
    required this.value,
    required this.createdOn,
    required this.modifiedOn,
  }});

  factory {class_name}.fromJson(Map<String, dynamic> json) {{
    return {class_name}(
      user: json['user'],
      type: json['type'],
      value: json['value'],
      createdOn: json['created_on'],
      modifiedOn: json['modified_on'],
    );
  }}

}}
"""

There are 3 basic chunks of code that need to be put into templates:

  1. The class properties
  2. The class constructor
  3. The class’s factory function to create using a json Map

The 3 basic chunks have one line for each property in the Django Model. What I do to create my code generator is to put in placeholders for the 3 chunks:

template = """
class {class_name} {{
  {class_properties}

  {class_name}({{
    {constructor_properties}
  }});

  factory {class_name}.fromJson(Map<String, dynamic> json) {{
    return {class_name}(
      {factory_mapping}
    );
  }}

}}
"""

I then create template generators for the individual parts:

class_property = "{is_final} {type} {property_name}"
constructor_property = "required this.{property_name}"
factory_mapping = "{property_name}: json['{python_name}'],"

The most challenging template is the “class_property” template.

You can organize your code in different ways. You can choose to include your templates right in your code generator or you can have them as a separate file. In my case, I include them in the same file. This makes it easier to copy around, but slightly more difficult to maintain.

In the next post I will go over mapping between Django and Flutter.

Similar Posts