ingdao 0.0.6 copy "ingdao: ^0.0.6" to clipboard
ingdao: ^0.0.6 copied to clipboard

outdated

Data Management Framework


id: flutter-sdk-ingdao title: IngDao sidebar_label: IngDao Getting Started #

IngDao is a data management framework for flutter mobile application. It encapsulates how the data source coming and persistance way from the service layer. The data soruce can be file, database, network or hybrid mode. Programmer just need to configure all the available data sources in one time and activate it, then programmer no need to know the data source exactly during data manipulating in the rest of the functions. They just need know the common data manipulate method like, fetch, delete, save, count...

Framework adopted the factory/repository concept. Factory will expose out the generic data function: save, delete, fetch and count. Repository will be used by the factory to provide actual implementation.

Currently, the framework support sqlite database and network data sources as repository term. For sqlite database, it will auto detect the sql table changes without user specify the migration plan and version. Currently the support mode is able to detect new field/new table be added. In future, will detect field/table be removed or even field type change. It support table joining, pagination, grouping and allow user select multiple fields from different table at once time without write pure sql.

Framework also ensure the data security by applying AES encryption for database, AES/DH for data network flow. But of course the backend need to follow the same way to receive and decrypt the AES data. SSL pinning supported too.

Programmer can create own repostiory by extends from DfBaseRepository, and provide the implementation. After that, programmer need to register the repository to factory.

The framework architecture concept is provided as below: -

Custom Repository #

You can create your own repository by implementing the following functions: -

import 'package:ingdao/dataflow/model/DfBaseModel.dart';
import 'package:ingdao/dataflow/model/DfBaseRepository.dart';

class DfMyCustomRepositoryModel extends DfBaseModel<DfMyCustomEntity> {
  String extraParamINeed;

  DfDbModel({@required String name, @required bool encrypt, @required this.extraParamINeed, @required List<DfMyCustomEntity> entities}) : super(name: name, encrypt: encrypt, entities: entities);

}

class DfMyCustomRepository extends DfBaseRepository<DfMyCustomRepositoryModel> {

  DfMyCustomRepository({@required DfMyCustomRepositoryModel model}) : super(model: model);

  Future<void> startInit() async {

  }

  Future<void> stop() async {

  } 

  Future<DfResponse> fetch(DfQuery query) async {

  }

  Future<List<DfResponse>> save(List<DfQuery> queries) async {

  }

  Future<List<DfResponse>> delete(List<DfQuery> queries) async {

  }
}

Then register and activate your new repository via DfFactory: -

  DfFactory instance = DfFactory();
  DfMyCustomRepository repMyCustom = DfMyCustomRepository(model: modelMyCustom); 
  instance.registerRepository("myRepositoyName", repMyCustom);
  await instance.activateRepository("myRepositoyName");

How to use repositories. #

  1. Define all the available repositories in Enum
const REPO_NAME_SQLITEDB = "sqlitedb";
const REPO_NAME_WEB = "web";

enum RepositoryType {
  sqlitedb,
  web
}

extension RepositoryTypeExtension on RepositoryType {

  String get name {
    switch (this) {
      case RepositoryType.sqlitedb:
        return REPO_NAME_SQLITEDB;
      case RepositoryType.web:
        return REPO_NAME_WEB;        
      default:
        return null;
    }
  }

  String desp() {
    return this.name;
  }
}

2. Start plan out the fields for the table entity and web http json response.

Example, create a 'RoleField' class that represent the user role fields container. The role object can be from database or HTTP response. The role contains 2 fields like id and name. This class also have one method call createRoleFieldBasedOnRepo(DfBaseRepository repo). It will based on pass in repository to return the related entity accordingly. One is the db entity and another one is web entity. The entity will be implemented at below later

import 'package:ingdao/dataflow/field/dfbasefield.dart';
import 'package:ingdao/dataflow/repository/dfbaserepository.dart';
import '../../../appsetting.dart';
import 'entrole.dart';
import 'entroleweb.dart';

abstract class RoleField {

  DfBaseField fId;
  DfBaseField fName;

  static RoleField createRoleFieldBasedOnRepo(DfBaseRepository repo) {
    switch (repo.model.name) {
      case REPO_NAME_SQLITEDB :
        return Role();
      case REPO_NAME_WEB :
        return RoleWeb();
    }
    return null;
  }
}

'UserField' class example that contains all the fields for the User object

import 'package:ingdao/dataflow/field/dfbasefield.dart';
import 'package:ingdao/dataflow/repository/dfbaserepository.dart';
import '../../../appsetting.dart';
import 'entuser.dart';
import 'entuserweb.dart';

abstract class UserField {

  DfBaseField fId;
  DfBaseField fPassword;
  DfBaseField fName;
  DfBaseField fEmail;
  DfBaseField fRoleid;
  DfBaseField fPicture;
  DfBaseField fRoleName;

  static UserField createUserFieldBasedOnRepo(DfBaseRepository repo) {
    switch (repo.model.name) {
      case REPO_NAME_SQLITEDB :
        return User();
      case REPO_NAME_WEB :
        return UserWeb();
    }
    return null;
  }
}

3. Create a user entity class. This class will be for database repository. The class will extends DfDbEntity and UserField together. Please look at the 'fRoleName' field. This field is not the db field under user table. But declare here is to store the sql fetch result for the role name of role table. so the sql look like: "select user.id, user.password, user.name, user.email, user.roleid, user.picture, role.name from user inner join role".

super(name: "appuser") - indicate the table name is "appuser"

import 'package:ingdao/dataflow/entity/dfbaseentity.dart';
import 'package:ingdao/dataflow/entity/dfdbentity.dart';
import 'package:ingdao/dataflow/field/dfbasefield.dart';
import 'package:ingdao/dataflow/field/dfdbfield.dart';
import 'package:ingdao/dataflow/field/dffieldtype.dart';
import 'entrole.dart';
import 'userfield.dart';

class User extends DfDbEntity with UserField {
    
  User ({String id, String password, String name, String email, String roleid, String picture}) : super(name: "appuser") {
    fId = DfDbField(entity: this, name: "id", primaryKey: true, joinKey: false, fieldtype: DfFieldType.text(), value: id);
    fPassword = DfDbField(entity: this, name: "password", primaryKey: false, joinKey: false, fieldtype: DfFieldType.text(), value: password);
    fName = DfDbField(entity: this, name: "name", primaryKey: false, joinKey: false, fieldtype: DfFieldType.text(), value: name);
    fEmail = DfDbField(entity: this, name: "email", primaryKey: false, joinKey: false, fieldtype: DfFieldType.text(), value: email);
    fRoleid = DfDbField(entity: this, name: "roleid", primaryKey: false, joinKey: true, fieldtype: DfFieldType.text(), value: roleid);
    fPicture = DfDbField(entity: this, name: "picture", primaryKey: false, joinKey: false, fieldtype: DfFieldType.text(), value: picture);    
    fRoleName = DfDbField(entity: Role(), name: "name", primaryKey: false, joinKey: false, fieldtype: DfFieldType.text(), displayOnly: true);
  }

  //Define the sqilite table fields available.
  //Define the fields will map back to the sqlite query executed reuslt: Map<String,dynamic>
  @override
  List<DfDbField> fields() {
     return [fId,fPassword,fName,fEmail,fRoleid,fPicture,fRoleName];
  }

  @override
  DfBaseEntity<DfBaseField> newInstance() {
    return User();
  }
}

DfDbField Parameter

  • Parameters:
    Name Type Nullable Remark
    entity DfBaseEntity N field entity
    name String N Sql table field name
    primaryKey bool N Is primary key field
    joinKey bool N will be use to join with another table
    type FieldType N Field type like text, real, int
    value dynamic Y Any value
    displayOnly bool N True means the field not under current entity but from other entity with display puporse only.

4. User entity class for web repository. The class will extends DfWebEntity and UserField. One extra method need to implement is "convertToWebReqParam(DfQuery query)" this will let the entity know how to define the HTTP request params, path variable and json body based on the DfQuery object.

Programmer can specify the web http method via query.purpose variable.

  • Parameters:
    Name Http Method
    Fetch GET
    ADD POST
    UPDATE_WHOLE_ENTITY_ATTRS PUT
    UPDATE_SOME_ENTITY_ATTRS PATCH
    DELETE DELETE
import 'package:ingdao/dataflow/control/dfquery.dart';
import 'package:ingdao/dataflow/control/dfqueryorderby.dart';
import 'package:ingdao/dataflow/entity/dfbaseentity.dart';
import 'package:ingdao/dataflow/entity/dfwebentity.dart';
import 'package:ingdao/dataflow/field/dfbasefield.dart';
import 'package:ingdao/dataflow/field/dfwebfield.dart';
import 'entroleweb.dart';
import 'userfield.dart';

class UserWeb extends DfWebEntity with UserField {
  //User({this.id,this.password,this.name,this.email,this.roleid}); 
  UserWeb ({String id, String password, String name, String email, String roleid, String picture}) : super(contextPath: "user") {
    //{@required DfBaseEntity entity, @required String name, @required this.primaryKey, @required this.joinKey, @required this.type, dynamic value}
    fId = DfWebField(entity: this, name: "id", value: id);
    fPassword = DfWebField(entity: this, name: "password", fieldtype: DfFieldType.text(), value: password);
    fName = DfWebField(entity: this, name: "name", fieldtype: DfFieldType.text(), value: name);
    fEmail = DfWebField(entity: this, name: "email", fieldtype: DfFieldType.text(), value: email);
    fRoleid = DfWebField(entity: this, name: "roleid", fieldtype: DfFieldType.text(), value: roleid);
    fPicture = DfWebField(entity: this, name: "picture", fieldtype: DfFieldType.text(), value: picture);
    fRoleName = DfWebField(entity: RoleWeb(), name: "rolename");    
  }

  //Define what are the fields will map back the result from http response Map<String,dynamic>
  @override
  List<DfWebField> fields() {
     return [fId,fPassword,fName,fEmail,fRoleid,fPicture,fRoleName];
  }

  @override
  DfWebReqParam convertToWebReqParam(DfQuery query) {
    DfWebReqParam param = DfWebReqParam();
    param.headers["token"] = Session.token;

    if (query.purpose == DfQueryPurpose.FETCH) {
        param.reqParams["pageNumber"] = "${query.paging.pageNumber}";
        param.reqParams["perPageCount"] = "${query.paging.perPageCount}";

        int i = 0;
        for (var orderBy in query.orderBy) {
          ++i;
          param.reqParams["orderBy$i"] = orderBy.field.name;
          param.reqParams["asc$i"] = orderBy.orderByType ==OrderByType.asc ? "true" : "false";
        }

        if (query.criteriaGroup != null) {
          for (var cri in query.criteriaGroup.criterias) {
            param.reqParams[cri.field.name] = cri.values[0];
          }
        }

        return param;
    }
    return null;
  }

  @override
  DfBaseEntity<DfBaseField> newInstance() {
    return UserWeb();
  }
}

DfWebField Parameter

  • Parameters:
    Name Type Nullable Remark
    entity DfBaseEntity N field entity
    name String N json body field name
    value dynamic Y Any value

5. Create a Service layer class. A class that should not contains any code related to UI. The class should extends from DfService. Create 2 reposiories: Sqlitedb and web, then register under DfFactory. Activate one of them.

import 'package:ingdao/ingdao.dart';
import 'package:ingdao/dataflow/field/dfbasefield.dart';
import 'package:ingdao/dataflow/control/dfquery.dart';
import 'package:ingdao/dataflow/control/dfquerypaging.dart';
import 'package:ingdao/dataflow/control/dfqueryorderby.dart';
import 'package:ingdao/dataflow/control/dfresponse.dart';
import 'package:ingdao/dataflow/control/dfquerycriteriagroup.dart';
import 'package:ingdao/dataflow/control/dfquerycriteria.dart';
import 'package:ingdao/dataflow/control/dfqueryentityjoin.dart';
import 'package:ingdao/dataflow/service/dfservice.dart';
import 'package:ingdao/dataflow/model/dfwebmodel.dart';
import 'package:ingdao/dataflow/repository/dfwebrepository.dart';
import 'package:ingdao/dataflow/model/dfdbmodel.dart';
import 'package:ingdao/dataflow/repository/dfdbrepository.dart';
import 'package:ingdao/dataflow/entity/dfbaseentity.dart';

class AppService extends DfService {
  DfFactory dfFactory = DfFactory();

  @override
  startInit() async {
    AppSetting appSetting = AppSetting();

    //Web
    SslPinningCri sslPinningCri = SslPinningCri(shakeys: appSetting.sslPinningFingerPrints, shatype: SslPinningCriShaType.SHA256);
    DfWebModel modelWeb = DfWebModel(name: RepositoryType.web.name, 
                                    encrypt: appSetting.dataEncrypt, 
                                    entities: [RoleWeb(),UserWeb()], 
                                    serverUrl: appSetting.serverUrl, 
                                    commonHeaders: DfWebModel.formJsonHeader(authToken: appSetting.token), 
                                    sslPinningCri: sslPinningCri);

    DfWebRepository repWeb = DfWebRepository(model: modelWeb); 
    dfFactory.registerRepository(RepositoryType.web.name, repWeb);

    //Database
    DfDbModel modelDb = DfDbModel(name: RepositoryType.sqlitedb.name, 
                                  dbName: appSetting.dbName, 
                                  encrypt: appSetting.dataEncrypt, 
                                  entities: [Role(),User()]);
    DfDbRepository repDb = DfDbRepository(model: modelDb);
    dfFactory.registerRepository(RepositoryType.sqlitedb.name, repDb);

    //Activate
    //await dfFactory.activateRepository(RepositoryType.sqlitedb.name);
    await dfFactory.activateRepository(RepositoryType.web.name);
  }
}

DfDbModel Parameter

  • Parameters:
    Name Type Nullable Remark
    name String N repository name
    dbName String N database name
    encrypt bool N Database need to encrypt or not
    entities List N list of database entities

DfWebModel Parameter

  • Parameters:
    Name Type Nullable Remark
    name String N repository name
    serverUrl String N serverUrl e.g. https://www.myserver.com/rest/
    encrypt bool N Data transfer need to encrypt or not. AES will be used
    entities List N list of database entities
    sslPinningCri SslPinningCri N provide one or more fingerprints string for SSL pinning purpose

CRUD Sample. Add the following functions to the service class above. So this will be the data manipulation center. #

CRUD - Add Record #

DfQuery Parameter

  • Parameters:
    Name Type Nullable Remark
    purpose DfQueryPurpose N Http Method
    queryFields List N select query fields, or fields need to insert/save
    entityJoin DfQueryEntityJoin N List of entities involves and the join type like inner join, left join
    criteriaGroup DfQueryCriteriaGroup Y select criteria based on field value and operator like eq, neq, like all supported
    orderBy List Y order by one or more field with asc/desc support
    groupBy List Y Group by one or more field
    paging Paging Y Pagination by Page number and limit
void addSomeRole() {
  DfBaseEntity entRole = dfFactory.currentRepository.model.entities[0];

  List<DfBaseEntity> roles = List<DfBaseEntity>();

    RoleField roleSuperAdmin = RoleField.createRoleFieldBasedOnRepo(dfFactory.currentRepository);
    roleSuperAdmin.fId.value = "superadmin";
    roleSuperAdmin.fName.value = "Super Admin";
    roles.add(roleSuperAdmin as DfBaseEntity);

    List<DfQuery> roleInsertQueries = List<DfQuery>();
    roles.forEach((role) {
      DfQuery query = DfQuery(purpose: DfQueryPurpose.ADD, queryFields: role.toMap(), entityJoin: DfQueryEntityJoin(entities: [entRole])  );
      roleInsertQueries.add(query);
    });
    await dfFactory.save(roleInsertQueries);
}

CRUD - Update Record #

Change the "superadmin" role name to "General Admin";

void updateSomeRole() {
  DfBaseEntity entRole = dfFactory.currentRepository.model.entities[0];
  RoleField roleField = RoleField.createRoleFieldBasedOnRepo(dfFactory.currentRepository);

  List<DfBaseEntity> roles = List<DfBaseEntity>();

    RoleField roleSuperAdmin = RoleField.createRoleFieldBasedOnRepo(dfFactory.currentRepository);
    roleSuperAdmin.fName.value = "General Admin";
    roles.add(roleSuperAdmin as DfBaseEntity);

    List<DfQueryCriteria> criterias = [DfQueryCriteria(field: roleField.fId, oper: OperType.eq, values: ['superadmin'] )];
    List<DfQuery> roleInsertQueries = List<DfQuery>();
    roles.forEach((role) {
      DfQuery query = DfQuery(purpose: DfQueryPurpose.UPDATE_SOME_RNTITY_ATTRS, queryFields: role.toMap(), entityJoin: DfQueryEntityJoin(entities: [entRole]), criteriaGroup: DfQueryCriteriaGroup(criterias: criterias)  );
      roleInsertQueries.add(query);
    });
    await dfFactory.save(roleInsertQueries);
}

CRUD - Delete Record #

Change the "superadmin" role name to "General Admin";

void deleteSomeRole() {
  DfBaseEntity entRole = dfFactory.currentRepository.model.entities[0];
  RoleField roleField = RoleField.createRoleFieldBasedOnRepo(dfFactory.currentRepository);

  List<DfBaseEntity> roles = List<DfBaseEntity>();

    RoleField roleSuperAdmin = RoleField.createRoleFieldBasedOnRepo(dfFactory.currentRepository);
    roleSuperAdmin.fName.value = "General Admin";
    roles.add(roleSuperAdmin as DfBaseEntity);

    List<DfQueryCriteria> criterias = [DfQueryCriteria(field: roleField.fId, oper: OperType.eq, values: ['superadmin'] )];
    List<DfQuery> roleInsertQueries = List<DfQuery>();
    roles.forEach((role) {
      DfQuery query = DfQuery(purpose: DfQueryPurpose.DELETE, queryFields: role.toMap(), entityJoin: DfQueryEntityJoin(entities: [entRole]), criteriaGroup: DfQueryCriteriaGroup(criterias: criterias)  );
      roleInsertQueries.add(query);
    });
    await dfFactory.delete(roleInsertQueries);
}

CRUD - Fetch user record #

  DfQueryCriteria _formUserRoleCriteria() {
    RoleField role = RoleField.createRoleFieldBasedOnRepo(dfFactory.currentRepository);
    return DfQueryCriteria(field: role.fId, oper: OperType.eq, values: ['generaluser'] );
  }

  Future<FetchEntityResult<UserField>> fetchUser(String searchName, int pageNumber, int limit) async {
    DfBaseEntity entRole = dfFactory.currentRepository.model.entities[0];
    DfBaseEntity entUser = dfFactory.currentRepository.model.entities[1];
    UserField userField = UserField.createUserFieldBasedOnRepo(dfFactory.currentRepository);

    List<DfBaseField> selFields = [userField.fId, userField.fName, userField.fEmail, userField.fRoleName, userField.fPicture];

    List<DfQueryCriteria> criterias = [_formUserRoleCriteria()];
    if (searchName != null && searchName.length > 0) {
      criterias.add(DfQueryCriteria(field: userField.fName, oper: OperType.like, values: [searchName] ));
    }    

    DfQueryOrderBy orderBy =DfQueryOrderBy(field: userField.fName, orderByType: OrderByType.asc);
    Paging paging = Paging(pageNumber: pageNumber, perPageCount: limit);

    DfQuery query = DfQuery(purpose: DfQueryPurpose.FETCH, 
                            queryFields: selFields, 
                            entityJoin: DfQueryEntityJoin(entities: [entUser,entRole], 
                            joins: [JoinType.innerJoin]), 
                            criteriaGroup: DfQueryCriteriaGroup(criterias: criterias), 
                            orderBy: [orderBy], 
                            paging: paging);

    List<UserField> users = List<UserField>();
    DfResponse resp = await dfFactory.fetch(query);
    if (resp.error != null) {
      print("FetchUser Error: ${resp.error.message}");
    }
    else {
      List<Map<String,dynamic>> resultSet = resp.result;
      resultSet.forEach((result) {
        DfBaseEntity newUser = entUser.newInstance();
        print("Final result $result");
        newUser.fromMap(result);
        UserField uf = newUser as UserField;
        print("Final user role name ${uf.fRoleName.value}");
        users.add(uf);
      });
    }
    return FetchEntityResult<UserField>(entities: users, error: resp.error != null ? resp.error.message : null);
  }

Start initialize your service class #

class SplashScr extends StatelessWidget {
  final AppService appService = AppService();

  _loadAppService(BuildContext context) async {
    await appService.startInit();
      Navigator.pushReplacement(
      context,
      MaterialPageRoute(builder: (context) => UserListScr(appService: appService))
    );
  }

Full running example. #

Please look at example/usermgmt folder.