How to mock Flutter Fire Firebase Functions correctly? - flutter

I'm trying to mock the Firebase Functions package from Flutter Fire, but I keep getting errors.
This is my attempt at creating the Mock. But the call function is giving me errors when I try to override it because the return type is not correct.
library firebase_cloud_functions_mock;
import 'dart:convert';
import 'package:cloud_functions/cloud_functions.dart';
import 'package:mockito/mockito.dart';
class MockFirebaseFunctions extends Mock implements FirebaseFunctions {
final Map<String, String> _jsonStore = <String, String>{};
String _convertMapToJson(Map<String, dynamic> parameters) {
return json.encode(parameters);
}
void mockResult(
{String functionName, String json, Map<String, dynamic> parameters}) {
if (parameters?.isNotEmpty != null) {
// ignore: parameter_assignments
functionName = functionName + _convertMapToJson(parameters);
}
_jsonStore[functionName] = json;
}
String getMockResult(String functionName, Map<String, dynamic> parameters) {
// ignore: parameter_assignments
functionName = parameters == null
? functionName
: (parameters?.isNotEmpty != null
? functionName + _convertMapToJson(parameters)
: functionName);
assert(
_jsonStore[functionName] != null, 'No mock result for $functionName');
return _jsonStore[functionName];
}
#override
HttpsCallable getHttpsCallable({String functionName}) {
return HttpsCallableMock._(this, functionName);
}
}
class HttpsCallableMock extends Mock implements HttpsCallable {
HttpsCallableMock._(this._firebaseFunctions, this._functionName);
final MockFirebaseFunctions _firebaseFunctions;
final String _functionName;
#override
Future<HttpsCallableResult> call([dynamic parameters]) {
final decoded = json.decode(_firebaseFunctions.getMockResult(
_functionName, parameters as Map<String, dynamic>));
return Future.value(HttpsCallableResultMock._(decoded));
}
/// The timeout to use when calling the function. Defaults to 60 seconds.
Duration timeout;
}
class HttpsCallableResultMock extends Mock implements HttpsCallableResult {
HttpsCallableResultMock._(this.data);
/// Returns the data that was returned from the Callable HTTPS trigger.
#override
final dynamic data;
}
Does anyone know how to correctly mock the Firebase Functions package from Flutter Fire?

I was able to make your example work with cloud_function-2.0.0 with three small changes.
Adding the <T> type parameters in HttpsCallableMock.call and
HttpsCallableResultMock.
Replacing the override of
getHttpsCallable with httpsCallable.
Checking parameters?.isNotEmpty == true instead != null. This change might not be needed depending on the behavior you want when empty params are passed.
Here is the updated code.
import 'package:cloud_functions/cloud_functions.dart';
import 'package:mockito/mockito.dart';
class MockFirebaseFunctions extends Mock implements FirebaseFunctions {
final Map<String, String> _jsonStore = <String, String>{};
String _convertMapToJson(Map<String, dynamic> parameters) {
return json.encode(parameters);
}
void mockResult(
{String functionName, String json, Map<String, dynamic> parameters}) {
if (parameters?.isNotEmpty == true) {
functionName = functionName + _convertMapToJson(parameters);
}
_jsonStore[functionName] = json;
}
String getMockResult(String functionName, Map<String, dynamic> parameters) {
// ignore: parameter_assignments
functionName = parameters == null
? functionName
: (parameters?.isNotEmpty == true
? functionName + _convertMapToJson(parameters)
: functionName);
assert(
_jsonStore[functionName] != null, 'No mock result for $functionName. \n Expected one of ${_jsonStore.keys}');
return _jsonStore[functionName];
}
#override
HttpsCallable httpsCallable(String functionName, {HttpsCallableOptions options}) {
return HttpsCallableMock._(this, functionName);
}
}
class HttpsCallableMock extends Mock implements HttpsCallable {
HttpsCallableMock._(this._firebaseFunctions, this._functionName);
final MockFirebaseFunctions _firebaseFunctions;
final String _functionName;
#override
Future<HttpsCallableResult<T>> call<T>([dynamic parameters]) {
final decoded = json.decode(_firebaseFunctions.getMockResult(
_functionName, parameters as Map<String, dynamic>));
return Future.value(HttpsCallableResultMock<T>._(decoded));
}
/// The timeout to use when calling the function. Defaults to 60 seconds.
Duration timeout;
}
class HttpsCallableResultMock<T> extends Mock implements HttpsCallableResult<T> {
HttpsCallableResultMock._(this.data);
/// Returns the data that was returned from the Callable HTTPS trigger.
#override
final T data;
}

Related

Flutter generic type does not work with named constructor T foo(data) => T.fromJson(data);

I am trying to create a generic function in my APIClient service. But in the dart, I can't call named constructors with generic functions.
My goal here is to make a generic function so that I can just pass the Type like
ApiClient.instance.get<SomeClass>("some url");
and get an return of Future<SomeClass?>. Please help me out if with these.
Is there any way to do this or any better implementation than this?
import 'package:http/http.dart' as http;
abstract class JSONserializable {
Map<String, dynamic> toJson();
JSONserializable();
JSONserializable.fromJson(Map<String, dynamic> json);
}
abstract class Data extends JSONserializable {
Data();
#override
factory Data.fromJson(Map<String, dynamic> json) =>
throw UnimplementedError();
#override
Map<String, dynamic> toJson();
Data copyWith();
#override
String toString() => jsonEncode(toJson());
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is Data &&
runtimeType == other.runtimeType &&
toString() == other.toString();
#override
int get hashCode => toString().hashCode;
}
class ChessProblem implements Data {
ChessProblem();
#override
Map<String, dynamic> toJson() {
return {};
}
#override
ChessProblem.fromJson(Map<String, dynamic> json) {
//ToDo: implement fromJson
}
#override
ChessProblem copyWith() {
return ChessProblem();
}
}
class ApiClient {
static final ApiClient _instance = ApiClient._internal();
factory ApiClient() => _instance;
ApiClient._internal();
Future<R?> get<R extends Data>(String url) async {
var response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
var json = jsonDecode(response.body);
return R.fromJson(json);
} else {
return null;
}
}
}

Having trouble creating generic Dart constructor for Json conversion

To all Dart gurus: I'm trying to implement a generic networking layer in Dart that converts REST service response to a specified model class:
// The idea is to make a network call and get a deserialized model as a response:
final token =
await _authNetworkService.execute<AccessTokenResponse>(request);
Here is the implementation:
// Model interface
abstract class JsonConvertible {
Map<String, dynamic> toJson();
JsonConvertible.fromJson(Map<String, dynamic> json);
}
// Model
class AccessTokenResponse extends JsonConvertible {
String? accessToken;
#override
Map<String, dynamic> toJson() {
return {};
}
#override
AccessTokenResponse.fromJson(Map<String, dynamic> json)
: super.fromJson(json) {
accessToken = json['access_token'];
}
}
// Network response class
class NetworkResponse<Model> {
Model data;
NetworkResponse.ok(this.data);
}
// Class to create a valid network service request
class NetworkRequest {
}
// Class that performs all network calls
class NetworkService {
Future<NetworkResponse<M>> execute<M extends JsonConvertible>(NetworkRequest request) async {
// For simplicity replaced all DIO calls with static data:
final response = {'data': {'access_token': 'XXX'}};
return NetworkResponse.ok(M.fromJson(response['data'])); //<- Fails here with error: Method 'fromJson' isn't defined for the type 'Type'...
}
}
DartPad: https://dartpad.dev/?id=9a29a7e49a084e69fd1d8078d5f2b977
How can I achieve expected behavior?
one way you can solve this is by passing the fromJson constructor as an argument to the execute function but this will add another step for every time execute is called
// Class that performs all network calls
class NetworkService {
Future<NetworkResponse<M>> execute<M extends JsonConvertible>(NetworkRequest request,M Function(Map<String, dynamic>) parser ) async {
// For simplicity replaced all DIO calls with static data:
final response = {'data': {'access_token': 'XXX'}};
return NetworkResponse.ok( parser(response['data']!)); //<- Fails here with error: Method 'fromJson' isn't defined for the type 'Type'...
}
}
and this is how you would call the execute function
final token =
await _authNetworkService.execute<AccessTokenResponse>(request,AccessTokenResponse.fromJson);

flutter cannot write in hive: HiveError: Cannot write, unknown type: RecipeModel. Did you forget to register an adapter?

I have a model of Recipes and as the name suggests the model is for recipes. The recipe model has
name,
authorName,
category
List --> separate model
Lis --->separate model
And toJson and fromJson methods. Now I want a list which will be saved locally, the list will be of all the recipes the user has marked as favorite.
So, I made this FavoriteRecipeModel:
class FavoriteRecipeModel {
List<RecipeModel>? recipeList;
FavoriteRecipeModel({this.recipeList});
factory FavoriteRecipeModel.fromJson(Map<String, dynamic> json) =>
FavoriteRecipeModel(
recipeList: json["recipeList"] == null ? null : json["recipeList"],
);
Map<String, dynamic> toJson() => {
"recipe": recipeList == null ? null : recipeList,
};
}
And this is how the function is written:
FavoriteRecipeModel frm = FavoriteRecipeModel();
void addToFavorites() async {
await Hive.initFlutter();
var box = await Hive.openBox('favoriteRecipeList');
box.put('frm1', widget.recipe!);
print(frm);
}
The widget.recipe! is coming in like this:
final RecipeModel? recipe;
RecipeDetailsScreen({required this.recipe});
I'm using the recipe model to load all the data in the screen.
But I'm getting:
Unhandled Exception: HiveError: Cannot write, unknown type: RecipeModel. Did you forget to register an adapter?
What did I do wrong and how can I fix it?
Update 1: here's my full FavoritesModel
class FavoriteRecipeModel {
List<RecipeModel>? recipeList;
FavoriteRecipeModel({this.recipeList});
factory FavoriteRecipeModel.fromJson(Map<String, dynamic> json) =>
FavoriteRecipeModel(
recipeList: json["recipeList"] == null ? null : json["recipeList"],
);
Map<String, dynamic> toJson() => {
"recipe": recipeList == null ? null : recipeList,
};
}
class FavoriteAdapter extends TypeAdapter<FavoriteRecipeModel> {
#override
final typeId = 0;
#override
FavoriteRecipeModel read(BinaryReader reader) {
return FavoriteRecipeModel();
}
#override
void write(BinaryWriter writer, FavoriteRecipeModel obj) {
// TODO: implement write
writer.write(obj.recipeList);
}
}
and here's the method that should list the all recipe's selected as favorite:
FavoriteRecipeModel frm = FavoriteRecipeModel();
void addToFavorites() async {
Hive.registerAdapter(FavoriteAdapter());
await Hive.initFlutter();
var box = await Hive.openBox<FavoriteRecipeModel>('favoriteRecipeList');
box.put('frm1', FavoriteRecipeModel());
print(box.values);
}
It's in the hive docs, as you are trying to serialize a custom object Hive does not know how to do that. In which case you should create and register a TypeAdapter.

Instance of 'Response<dynamic>' flutter Api Consumption

I am trying to make a post request in flutter using chopper. I have made an ApiService.dart file as
a generator file.
import 'package:bindle/Chopper/Models/LoginResponse.dart';
import 'package:chopper/chopper.dart';
part 'ApiService.chopper.dart';
#ChopperApi(baseUrl: 'http://192.168.1.20/bindal/api/v1/user/')
abstract class ApiService extends ChopperService {
#Post(path: "login")
Future<Response<LoginResponse>> doLogin([
#Header('auth_key') String authType,
#Query('email') String email,
#Query('password') String password,
#Query('userType') String userType,
]);
static ApiService create() {
final client = ChopperClient(
// The first part of the URL is now here
baseUrl: 'http://192.168.1.20/bindal/api/v1/user/',
services: [
// The generated implementation
_$ApiService(),
],
interceptors: [
HttpLoggingInterceptor()
],
// Converts data to & from JSON and adds the application/json header.
converter: JsonConverter(),
);
// The generated class with the ChopperClient passed in
return _$ApiService(client);
}
}
And this is my generated file.
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'ApiService.dart';
// **************************************************************************
// ChopperGenerator
// **************************************************************************
class _$ApiService extends ApiService {
_$ApiService([ChopperClient client]) {
if (client == null) return;
this.client = client;
}
final definitionType = ApiService;
Future<Response<LoginResponse>> doLogin(
[String authType, String email, String password, String userType]) {
final $url = 'http://192.168.1.20/bindal/api/v1/user/login';
final Map<String, dynamic> $params = {
'email': email,
'password': password,
'userType': userType
};
final $headers = {'auth_key': authType};
final $request = Request('POST', $url, client.baseUrl,
parameters: $params, headers: $headers);
return client.send<LoginResponse, LoginResponse>($request);
}
}
Next what i Did is i generated a model class called as LoginResponse where I have to fetch the data.
abstract class LoginResponse implements Built<LoginResponse, LoginResponseBuilder> {
int get status;
String get message;
LoginResponse._();
factory LoginResponse([void Function(LoginResponseBuilder) updates]) = _$LoginResponse;
static LoginResponse fromJson(String jsonString){
return serializers.deserializeWith(LoginResponse.serializer, json.decode(jsonString));
}
static Serializer<LoginResponse> get serializer => _$loginResponseSerializer;
}
this is the generated file for the above LoginResponse.dart file using built_value generator
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'LoginResponse.dart';
// **************************************************************************
// BuiltValueGenerator
// **************************************************************************
Serializer<LoginResponse> _$loginResponseSerializer =
new _$LoginResponseSerializer();
class _$LoginResponseSerializer implements StructuredSerializer<LoginResponse> {
#override
final Iterable<Type> types = const [LoginResponse, _$LoginResponse];
#override
final String wireName = 'LoginResponse';
#override
Iterable<Object> serialize(Serializers serializers, LoginResponse object,
{FullType specifiedType = FullType.unspecified}) {
final result = <Object>[
'status',
serializers.serialize(object.status, specifiedType: const FullType(int)),
'message',
serializers.serialize(object.message,
specifiedType: const FullType(String)),
];
return result;
}
#override
LoginResponse deserialize(
Serializers serializers, Iterable<Object> serialized,
{FullType specifiedType = FullType.unspecified}) {
final result = new LoginResponseBuilder();
final iterator = serialized.iterator;
while (iterator.moveNext()) {
final key = iterator.current as String;
iterator.moveNext();
final dynamic value = iterator.current;
switch (key) {
case 'status':
result.status = serializers.deserialize(value,
specifiedType: const FullType(int)) as int;
break;
case 'message':
result.message = serializers.deserialize(value,
specifiedType: const FullType(String)) as String;
break;
}
}
return result.build();
}
}
class _$LoginResponse extends LoginResponse {
#override
final int status;
#override
final String message;
factory _$LoginResponse([void Function(LoginResponseBuilder) updates]) =>
(new LoginResponseBuilder()..update(updates)).build();
_$LoginResponse._({this.status, this.message}) : super._() {
if (status == null) {
throw new BuiltValueNullFieldError('LoginResponse', 'status');
}
if (message == null) {
throw new BuiltValueNullFieldError('LoginResponse', 'message');
}
}
#override
LoginResponse rebuild(void Function(LoginResponseBuilder) updates) =>
(toBuilder()..update(updates)).build();
#override
LoginResponseBuilder toBuilder() => new LoginResponseBuilder()..replace(this);
#override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is LoginResponse &&
status == other.status &&
message == other.message;
}
#override
int get hashCode {
return $jf($jc($jc(0, status.hashCode), message.hashCode));
}
#override
String toString() {
return (newBuiltValueToStringHelper('LoginResponse')
..add('status', status)
..add('message', message))
.toString();
}
}
class LoginResponseBuilder
implements Builder<LoginResponse, LoginResponseBuilder> {
_$LoginResponse _$v;
int _status;
int get status => _$this._status;
set status(int status) => _$this._status = status;
String _message;
String get message => _$this._message;
set message(String message) => _$this._message = message;
LoginResponseBuilder();
LoginResponseBuilder get _$this {
if (_$v != null) {
_status = _$v.status;
_message = _$v.message;
_$v = null;
}
return this;
}
#override
void replace(LoginResponse other) {
if (other == null) {
throw new ArgumentError.notNull('other');
}
_$v = other as _$LoginResponse;
}
#override
void update(void Function(LoginResponseBuilder) updates) {
if (updates != null) updates(this);
}
#override
_$LoginResponse build() {
final _$result =
_$v ?? new _$LoginResponse._(status: status, message: message);
replace(_$result);
return _$result;
}
}
finally i called my api in the login page as
void doLogin(String email, String pass, BuildContext context) async {
try {
final response = await Provider.of<ApiService>(context)
.doLogin("d1d2fe0514f7d5c748c0e7e085b36f74","arpit1692#gmail.com",
"e10adc3949ba59abbe56e057f20f883e","App");
print(response.body);
} catch (e) {
print(e);
}
}
Which ultimately gives me Exception as => Instance of 'Response dynamic'
Please help me for what I am doing wrong.
The reason why the Response returns dyanmic is because the response wasn't serialized to the model you've defined. If LoginResponse is the model that should be used for the response, the LoginResponse class should have fromJson() that should serialize the json response. You can follow this guide to help you manage json serialization.

Flutter Internationalization: How to access to nested data in json

I want to know how to access to nested data in json.
AppLocalizations.of(context).translate('Information.about');
en.json
{
"Information" : {
"about": "About"
}
}
I tried like the way above but it cannot access to the nested data.
And here is translate method.
class AppLocalizations {
final Locale locale;
AppLocalizations(this.locale);
static AppLocalizations of(BuildContext context) {
return Localizations.of<AppLocalizations>(context, AppLocalizations);
}
// Static member to get access to the delegate from 'main.dart' file
static const LocalizationsDelegate<AppLocalizations> delegate = _AppLocalizationsDelegate();
Map<String, String> _localizedValues;
Future<bool> load() async {
// Load a language JSON file from the 'i18n' folder
String value = await rootBundle.loadString('i18n/${locale.languageCode}.json');
Map<String, dynamic> jsonMap = jsonDecode(value);
_localizedValues = jsonMap.map((key, value) {
return MapEntry(key, value.toString());
});
return true;
}
String translate(String key) {
// Returns a localized text
return _localizedValues[key];
}
}
This is my solution based on https://stackoverflow.com/a/60924513
Map flattenTranslations(Map<String, dynamic> json, [String prefix = '']) {
final Map<String, String> translations = {};
json.forEach((String key, dynamic value) {
if (value is Map) {
translations.addAll(flattenTranslations(value, '$prefix$key.'));
} else {
translations['$prefix$key'] = value.toString();
}
});
return translations;
}
Future<bool> load() async {
String jsonString = await rootBundle.loadString('assets/i18n/${locale.languageCode}.json');
Map<String, dynamic> jsonMap = jsonDecode(jsonString);
_localizedStrings = flattenTranslations(jsonMap);
return true;
}
And this is all app_localizations.dart file
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class AppLocalizations {
final Locale locale;
AppLocalizations(this.locale);
static AppLocalizations of(BuildContext context) {
return Localizations.of<AppLocalizations>(context, AppLocalizations);
}
static const LocalizationsDelegate<AppLocalizations> delegate = _AppLocalizationsDelegate();
Map<String, String> _localizedStrings;
Map flattenTranslations(Map<String, dynamic> json, [String prefix = '']) {
final Map<String, String> translations = {};
json.forEach((String key, dynamic value) {
if (value is Map) {
translations.addAll(flattenTranslations(value, '$prefix$key.'));
} else {
translations['$prefix$key'] = value.toString();
}
});
return translations;
}
Future<bool> load() async {
String jsonString = await rootBundle.loadString('assets/i18n/${locale.languageCode}.json');
Map<String, dynamic> jsonMap = jsonDecode(jsonString);
_localizedStrings = flattenTranslations(jsonMap);
return true;
}
String translate(String key) {
return _localizedStrings[key] ?? key;
}
}
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
const _AppLocalizationsDelegate();
#override
bool isSupported(Locale locale) {
return ['en', 'pl'].contains(locale.languageCode);
}
#override
Future<AppLocalizations> load(Locale locale) async {
AppLocalizations localizations = new AppLocalizations(locale);
await localizations.load();
return localizations;
}
#override
bool shouldReload(_AppLocalizationsDelegate old) => false;
}
Try this:
First Method:
AppLocalizations.of(context).translate(“about”);
And change your translate function to this:
String translate(String key) {
// Returns a localized text
return _localizedValues[“Information”][key];
}
Or you can do this:
Second Method:
AppLocalizations.of(context).translate(”Information”,“about”);
And change your translate function to this:
String translate(String parentkey, String nestedKey) {
// Returns a localized text
return _localizedValues[parentKey][nestedKey];
}
This might help.
Also, This is a good article to learn how to parse complex json files
UPDATED ANSWER:
After trying the code, I could understand the problem.
The problem is your _localizedValues["Information"] will be a String not a map becuase we converted the value to value.toString() and that's why you cannot use a second key because the returned object is not a Map but it's a String.
So _localizedValues["Information"] is "{about: About}".
To solve the problem, use the code below:
Map<String, dynamic> _localizedValues; //your values are not String anymore and we use dynamic instead
Future<bool> load() async {
// Load a language JSON file from the 'i18n' folder
String value = await rootBundle.loadString('i18n/${locale.languageCode}.json');
Map<String, dynamic> jsonMap = jsonDecode(value);
_localizedValues = jsonMap.map((key, value) {
return MapEntry(key, value); //not value.toString() so the value will be a map
});
return true;
}
String translate(String parentkey, String nestedKey) {
// Returns a localized text
return _localizedValues[parentKey][nestedKey];
}
And then you have to get "About" from the code below:
AppLocalizations.of(context).translate('Information','about');
This will allow you to access nested JSON regardless of depth. Just modify your translate method like so:
String translate(String key) {
List<dynamic> keys = key.split('.');
var value = keys.fold(_localizedValues, (obj, key) => obj[key]);
return value;
}
Then you are able to access your nested JSON in your code like this:
AppLocalizations.of(context).translate('key1.key2.key3');
Just make sure there won't be any periods in your JSON keys
If you are using easy_localization flutter package for localization you can access object value from JSON like below with dot notation
"Information.about".tr()
Here is my answer, it looks like #Morez answer but it works for me:
Map<String,dynamic> _localizedValues;
Map<String,dynamic> _localizedValues2;
Future<void> load() async {
String jsonStringValues = await rootBundle.loadString('lib/jsonLanguages/${locale.languageCode}.json');
//print(jsonStringValues);
Map<String,dynamic> mappedJson = json.decode(jsonStringValues);
_localizedValues = mappedJson.map((key, value) => MapEntry(key, value));
}
String translate(String key,String subkey){
String test = _localizedValues[key].toString();
test = test.replaceAllMapped(RegExp(r'(\b\w+( +\w+)*\b)|(\b\w+\b)'), (match) {return '"${match.group(0)}"';});
//print(test);
Map<String,dynamic> mappedJson2 = json.decode(test);
_localizedValues2 = mappedJson2.map((subkey, value) => MapEntry(subkey, value));
return _localizedValues2[subkey];
}