Skip to content

Instantly share code, notes, and snippets.

@channainfo
Last active August 26, 2021 00:17
Show Gist options
  • Save channainfo/ddd73b5bdf721f7fe4baa87a9a0e595d to your computer and use it in GitHub Desktop.
Save channainfo/ddd73b5bdf721f7fe4baa87a9a0e595d to your computer and use it in GitHub Desktop.
Flutter API connection to Oauth2 REST API
import 'package:vtenh/models/account_model.dart';
import 'package:vtenh/models/base_model.dart';
import 'package:vtenh/services/apis/base_resource_owner_api.dart';
class AccountApi extends BaseResourceOwnerApi {
@override
bool get useJapxDecode => true;
AccountModel objectTransformer(Map<String, dynamic> json) {
return AccountModel.fromJson(json);
}
Future<dynamic> create(BaseModel record, {Map<String, dynamic>? options}) async {
var result = await super.create(record);
return result;
}
String nameInUrl() {
return 'account';
}
String nameInParams() {
return 'user';
}
}
import 'dart:convert';
import 'package:japx/japx.dart';
import 'package:vtenh/constants/api_constant.dart';
import 'package:vtenh/exceptions/network_exception.dart';
import 'package:vtenh/mixins/json_mappable.dart';
import 'package:vtenh/models/base_model.dart';
import 'package:vtenh/models/cart/links_model.dart';
import 'package:vtenh/models/meta_model.dart';
import 'package:vtenh/models/network_error_model.dart';
import 'package:vtenh/models/object_url_model.dart';
import 'package:vtenh/networks/base_network.dart';
import 'package:dio/dio.dart';
import 'package:vtenh/storages/langs_storage.dart';
abstract class BaseApi with JsonMappable {
BaseNetwork? network;
Response<dynamic>? response;
var error;
NetWorkErrorModel? networkError;
bool get useJapxDecode => false;
bool get tranformResponse => true;
BaseApi() {
network = buildNetwork();
}
BaseNetwork buildNetwork();
success() {
if (this.response != null) {
return this.response!.statusCode! >= 200 && this.response!.statusCode! < 300;
}
return false;
}
// {"error":"Email has already been taken","errors":{"email":["has already been taken"]}}
Map<String, dynamic>? errors() {
if (error == null) {
return null;
}
if (error.response == null) {
return null;
}
if (error is DioError) {
var json = (error as DioError).response?.data;
var result = jsonDecode(json);
return result;
} else {
return null;
}
}
///@return Email has already been taken
String? errorMessage() {
if (error == null) {
return null;
}
if (error.response != null) {
return errors()?['error'];
} else {
return error.message;
}
}
beforeRunQueryExec(Function body) async {
try {
var result = await body();
return result;
} on DioError catch (err) {
if (err is DioError) {
NetWorkException netWorkException = NetWorkException(err);
NetWorkErrorModel _networkError = netWorkException.translateException();
networkError = _networkError;
}
error = err;
return null;
}
}
runQueryExec(Function queryBody) {
this.response = null;
this.error = null;
return beforeRunQueryExec(queryBody);
}
getIncluded() {
var json = jsonDecode(response.toString());
var result = json['included'] ?? [];
return result;
}
_fetchOne({String? id, Map<String, dynamic> options = const {}}) async {
var endpoint = objectNameUrlModel.fetchOneUrl(id);
response = await network?.http?.get(
endpoint,
queryParameters: options,
);
if (response.toString().isEmpty) return;
var json = jsonDecode(response.toString());
var item;
if (useJapxDecode) {
final decoded = Japx.decode(json);
item = decoded['data'];
} else {
var included = json['included'];
item = mergeAttr(json?['data'], included: included);
}
var result = objectTransformer(item);
return result;
}
///options example:
///```
///var options = {
/// "included": "line_items,variants",
/// "fields": {
/// "line_item": "name",
/// "variant": "sku,price",
/// "cart": "number",
/// },
/// "order_token": token,
///};
///```
///get option type, value and variant
///```
///{{baseUrl}}/products/lamborghini-baby-walker?fields[product]=slug&fields[option_type]=name&include=variants,option_types,option_types.option_values
///```
fetchOne({String? id, bool collection = true, Map<String, dynamic>? options}) async {
if (id == null) assert(collection == false);
var result = await runQueryExec(() async {
final _options = await getLocaleOptions(options: options);
return await _fetchOne(id: id, options: _options!);
});
return result;
}
_fetchAll({Map<String, dynamic>? queryParameters}) async {
var endpoint = objectNameUrlModel.fetchAllUrl();
response = await network?.http?.get(endpoint, queryParameters: queryParameters);
var json = jsonDecode(response.toString());
var data = json;
if (useJapxDecode) {
data = Japx.decode(json);
}
var result = itemsTransformer(data);
return result;
}
fetchAll({Map<String, dynamic>? queryParameters}) async {
var result = await runQueryExec(() async {
if (queryParameters != null && !queryParameters.containsKey('per_page')) {
queryParameters['per_page'] = ApiConstant.recordPerPage;
}
final _queryParameters = await getLocaleOptions(options: queryParameters);
return await _fetchAll(queryParameters: _queryParameters);
});
return result;
}
_update({String? id, Map<String, dynamic>? params, Map<String, dynamic>? options, bool collection = true}) async {
var endpoint = objectNameUrlModel.updatelUrl(id: id, options: options, collection: collection);
response = await network?.http?.patch(endpoint, data: params);
if (response == null || response.toString().isEmpty) return;
if (response?.headers.map['content-type']?.join().contains('text/html') == true) return;
var json = jsonDecode(response.toString());
var item;
if (useJapxDecode) {
final decoded = Japx.decode(json);
item = decoded['data'];
} else {
var included = json['included'];
item = mergeAttr(json?['data'], included: included);
}
var result = objectTransformer(item);
return result;
}
Future update(
{String? id, Map<String, dynamic>? params, Map<String, dynamic>? options, bool collection = true}) async {
var result = runQueryExec(() async {
final _options = await getLocaleOptions(options: options);
return await _update(id: id, params: params, options: _options, collection: collection);
});
return result;
}
_delete({
String? id,
Map<String, dynamic>? params,
Map<String, dynamic>? options,
bool collection = true,
}) async {
var endpoint = objectNameUrlModel.deletelUrl(id: id, options: options, collection: collection);
response = await network?.http?.delete(endpoint, data: params);
if (response.toString().isEmpty) return;
var json = jsonDecode(response.toString());
var item;
if (useJapxDecode) {
final decoded = Japx.decode(json);
item = decoded['data'];
} else {
var included = json['included'];
item = mergeAttr(json?['data'], included: included);
}
var result = objectTransformer(item);
return result;
}
delete({
String? id,
Map<String, dynamic>? params,
Map<String, dynamic>? options,
bool collection = true,
}) async {
var result = await runQueryExec(() async {
final _options = await getLocaleOptions(options: options);
return await _delete(id: id, params: params, options: _options, collection: collection);
});
return result;
}
// to request with hash parameters
// which return response as an object
// Ex: {{BASE_API}}/api/v2/internal/account_availability_checker?mobile_phone=0964142657
_queryOne({Map<String, dynamic>? queryParameters}) async {
var endpoint = objectNameUrlModel.queryOneUrl();
response = await network?.http?.get(endpoint, queryParameters: queryParameters);
var json = jsonDecode(response.toString());
var item = json['data'];
var result = objectTransformer(item);
return result;
}
// to request with hash parameters
// which return response as an object
// Ex: {{BASE_API}}/api/v2/internal/account_availability_checker?mobile_phone=0964142657
queryOne({Map<String, dynamic>? queryParameters}) {
var result = runQueryExec(() async {
final _queryParameters = await getLocaleOptions(options: queryParameters);
return await _queryOne(queryParameters: _queryParameters);
});
return result;
}
_create(BaseModel record, {Map<String, dynamic>? options}) async {
var endpoint = objectNameUrlModel.createUrl(options);
var data = buildParams(record);
response = await network?.http?.post(endpoint, data: data);
// some api return empty body even resoruce created
// var jsonString = response.toString().isEmpty ? "{}" : response.toString();
// var result = transformJsonToObject(jsonString);
if (response?.toString().isEmpty == true) return '{}';
var json = jsonDecode(response.toString());
if (tranformResponse == false) return json;
var item;
if (useJapxDecode) {
final decoded = Japx.decode(json);
item = decoded['data'];
} else {
var included = json['included'];
item = mergeAttr(json?['data'], included: included);
}
var result = item is Map<String, dynamic> ? objectTransformer(item) : null;
return result;
}
buildParams(BaseModel record) {
if (this.nameInParams().isEmpty) {
var result = record.createParams();
return result;
}
return {'${this.nameInParams()}': record.createParams()};
}
create(BaseModel record, {Map<String, dynamic>? options}) async {
var result = await runQueryExec(() async {
final _options = await getLocaleOptions(options: options);
return await _create(record, options: _options);
});
return result;
}
BaseModel transformJsonToObject(String jsonString) {
var json = jsonDecode(jsonString);
var item;
if (useJapxDecode) {
final decoded = Japx.decode(json);
item = decoded['data'];
} else {
var included = json['included'];
item = mergeAttr(json?['data'], included: included);
}
return objectTransformer(item);
}
objectTransformer(Map<String, dynamic> json);
itemsTransformer(Map<String, dynamic> json) {}
MetaModel buildMetaModel(Map<String, dynamic> json) {
var meta = MetaModel.fromJson(json['meta'] ?? {});
return meta;
}
/// if `currentLocale == null`, use default locale from api
Future<Map<String, dynamic>?> getLocaleOptions({Map<String, dynamic>? options}) async {
if (options != null && !options.containsKey('locale')) {
final langStorage = LangsStorage();
final currentLocale = await langStorage.getLocale();
if (currentLocale == null) {
return options;
} else {
options['locale'] = currentLocale.languageCode;
}
}
return options;
}
List<dynamic> buildItemsList(Map<String, dynamic> json) {
var items = json['data'];
var records = items.map((item) {
Map<String, dynamic>? attrs;
if (useJapxDecode) {
attrs = item;
} else {
attrs = mergeAttr(item);
}
var record = objectTransformer(attrs ?? {});
return record;
}).toList();
return records;
}
Links? buildLinkModel(Map<String, dynamic> json) {
Map<String, dynamic> links = json['links'];
if (links.isEmpty) {
return null;
}
var result = Links(
self: links['self'],
next: links['next'],
last: links['last'],
prev: links['prev'],
first: links['first'],
);
return result;
}
List<dynamic> buildIncluded(Map<String, dynamic> json) {
return json['included'];
}
String nameInUrl();
String nameInParams() {
return '';
}
ObjectNameUrlModel get objectNameUrlModel {
return ObjectNameUrlModel(nameInUrl: nameInUrl(), path: ApiConstant.path);
}
}
import 'package:vtenh/constants/api_constant.dart';
import 'package:vtenh/networks/base_network.dart';
import 'package:vtenh/networks/user_token_network.dart';
import 'package:vtenh/services/apis/base_api.dart';
abstract class BaseResourceOwnerApi extends BaseApi {
@override
BaseNetwork buildNetwork() {
return UserTokenNetwork(baseUrl: ApiConstant.baseUrl);
}
}
@channainfo
Copy link
Author

Flutter API adapter to connect to REST API over OAuth2 protocol using password credential flow. This code is being used in the VTENH app. Feel free to use

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment