Removal of api key

Network calls,
integration of retrofit api
added backup api
This commit is contained in:
2022-09-19 22:13:37 +01:00
parent 5104fc674e
commit 8cc19f0dc9
16 changed files with 548 additions and 12 deletions

View File

@@ -1,6 +1,6 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:toast/toast.dart';
import 'BaseViewModel.dart';
import 'Utils/Constants.dart';
@@ -17,6 +17,7 @@ abstract class BaseStatelessWidget<T extends BaseViewmodel>
@override
Widget build(BuildContext parent) {
ToastContext().init(parent);
return Scaffold(
body: Container(
padding: const EdgeInsets.all(paddingGlobal),
@@ -48,7 +49,7 @@ abstract class BaseStatelessWidget<T extends BaseViewmodel>
);
}
void onModelReady(model) {}
void onModelReady(T model) {}
void onStarted() {}
}

View File

@@ -21,4 +21,12 @@ abstract class BaseViewmodel extends BaseViewModel{
_viewState = HasError(error);
notifyListeners();
}
dynamic getData() {
if (viewState.runtimeType is HasData) {
return (viewState as HasData).data;
} else {
return null;
}
}
}

View File

@@ -4,7 +4,6 @@ import 'package:easy_cc_flutter/views/DropDownBox.dart';
import 'package:easy_cc_flutter/views/EditText.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'BaseStatelessWidget.dart';
import 'Utils/Constants.dart';
@@ -57,4 +56,11 @@ class HomePage extends BaseStatelessWidget<MainViewModel> {
],
);
}
@override
void onModelReady(MainViewModel model) {
String selected1 = model.getConversionPair(SelectionType.conversionFrom);
String selected2 = model.getConversionPair(SelectionType.conversionTo);
model.setCurrencyRate(selected1, selected2);
}
}

View File

@@ -10,16 +10,20 @@ import 'locator.dart';
class MainViewModel extends BaseViewmodel {
final Repository _repository = locator<RepositoryImpl>();
double conversionRate = 1.4;
double conversionRate = 1.0;
String getConversionPair(SelectionType type) {
CurrencyPair pair = _repository.getConversionPair();
switch (type) {
case SelectionType.conversionFrom:
return pair.currencyOne != null ? pair.currencyOne! : listOfCurrencies[0];
return pair.currencyOne != null
? pair.currencyOne!
: listOfCurrencies[0];
case SelectionType.conversionTo:
return pair.currencyTwo != null ? pair.currencyTwo! : listOfCurrencies[0];
return pair.currencyTwo != null
? pair.currencyTwo!
: listOfCurrencies[0];
default:
throw NullThrownError();
}
@@ -27,6 +31,18 @@ class MainViewModel extends BaseViewmodel {
void setConversionPair(String fromCurrency, String toCurrency) {
_repository.setConversionPair(fromCurrency, toCurrency);
setCurrencyRate(fromCurrency, toCurrency);
}
void setCurrencyRate(String fromCurrency, String toCurrency) {
onStart();
_repository.getConversationRateFromApi(fromCurrency, toCurrency).then(
(value) {
conversionRate = value.rate != null ? value.rate! : 0.00;
onSuccess(value);
}, onError: (exception, _) {
onError(exception.message);
});
}
String convertInput(String? input, SelectionType type) {
@@ -42,4 +58,4 @@ class MainViewModel extends BaseViewmodel {
return (convertedInput / conversionRate).toStringAsFixed(2);
}
}
}
}

View File

@@ -0,0 +1,6 @@
extension CurrencyExtension on String {
String getCurrencyCode(){
return substring(0,3);
}
}

View File

@@ -0,0 +1,11 @@
class Currency {
String? from;
String? to;
double? rate;
Currency(this.from, this.to, this.rate);
}
abstract class Mapper {
Currency convert();
}

View File

@@ -0,0 +1,35 @@
import 'package:dio/dio.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:retrofit/retrofit.dart';
import '../model/Currency.dart';
part 'backupCurrencyApi.g.dart';
@RestApi(baseUrl: "https://api.frankfurter.app/")
abstract class BackupCurrencyApi {
factory BackupCurrencyApi(Dio dio, {String baseUrl}) = _BackupCurrencyApi;
@GET("latest?")
Future<HttpResponse<CurrencyResponse>> getCurrencyRate(@Query("from") String currencyFrom,
@Query("to") String currencyTo);
}
@JsonSerializable()
class CurrencyResponse implements Mapper{
String? data;
double amount;
Map<String, double>? rates;
String? base;
CurrencyResponse(this.data, this.amount, this.rates, this.base);
factory CurrencyResponse.fromJson(Map<String, dynamic> json) => _$CurrencyResponseFromJson(json);
Map<String, dynamic> toJson() => _$CurrencyResponseToJson(this);
@override
Currency convert() {
MapEntry<String, double>? entry = rates?.entries.elementAt(0);
return Currency(base, entry?.key, entry?.value);
}
}

View File

@@ -0,0 +1,54 @@
import 'package:dio/dio.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:retrofit/retrofit.dart';
import '../model/Currency.dart';
part 'currencyApi.g.dart';
@RestApi(baseUrl: "https://free.currencyconverterapi.com/api/v3/")
abstract class CurrencyApi {
factory CurrencyApi(Dio dio, {String baseUrl}) = _CurrencyApi;
@GET("/convert?")
Future<HttpResponse<ResponseObject>> getConversion(@Query("apiKey") String apiKey, @Query("q") String currency);
}
@JsonSerializable()
class ResponseObject implements Mapper{
dynamic query;
Map<String, CurrencyObject>? results;
ResponseObject({
this.query,
this.results
});
factory ResponseObject.fromJson(Map<String, dynamic> json) => _$ResponseObjectFromJson(json);
Map<String, dynamic> toJson() => _$ResponseObjectToJson(this);
@override
Currency convert() {
CurrencyObject? cur = results?.entries.elementAt(0).value;
return Currency(cur?.fr, cur?.to, cur?.val);
}
}
@JsonSerializable()
class CurrencyObject{
String? id;
String? fr;
String? to;
double? val;
CurrencyObject({
this.id,
this.fr,
this.to,
this.val
});
factory CurrencyObject.fromJson(Map<String, dynamic> json) => _$CurrencyObjectFromJson(json);
Map<String, dynamic> toJson() => _$CurrencyObjectToJson(this);
}

View File

@@ -0,0 +1,30 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:retrofit/retrofit.dart';
import '../../main.dart';
mixin SafeApiCall {
Future<T> getDataFromApiCall<T>(Future<HttpResponse<T>> apiCall) async {
try {
HttpResponse<T> httpResponse = await apiCall;
return httpResponse.data;
} on DioError catch(dioError) {
Map<String, dynamic>? errorResponse = dioError.response?.data;
String error;
if (errorResponse?["error"] != null){
error = errorResponse!["error"];
} else if (dioError.error != null){
error = dioError.error;
} else {
error = "Failed to retrieve data from api";
}
logger.e(dioError.error);
throw HttpException(error);
}
}
}

View File

@@ -1,5 +1,3 @@
import 'dart:ffi';
import 'package:shared_preferences/shared_preferences.dart';
import 'CurrencyPair.dart';

View File

@@ -1,6 +1,8 @@
import '../model/Currency.dart';
import '../prefs/CurrencyPair.dart';
abstract class Repository {
CurrencyPair getConversionPair();
Future<void> setConversionPair(String fromCurrency, String toCurrency);
Future<Currency> getConversationRateFromApi(String fromCurrency, String toCurrency);
}

View File

@@ -1,11 +1,22 @@
import 'dart:io';
import 'package:easy_cc_flutter/Utils/currencyUtils.dart';
import 'package:easy_cc_flutter/data/model/Currency.dart';
import 'package:easy_cc_flutter/data/prefs/CurrencyPair.dart';
import 'package:easy_cc_flutter/data/prefs/PreferenceProvider.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import '../../locator.dart';
import '../../main.dart';
import '../network/backupCurrencyApi.dart';
import '../network/currencyApi.dart';
import '../network/safeApiCall.dart';
import 'Repository.dart';
class RepositoryImpl extends Repository {
class RepositoryImpl extends Repository with SafeApiCall {
final PreferenceProvider _prefs = locator<PreferenceProvider>();
final CurrencyApi _api = locator<CurrencyApi>();
final BackupCurrencyApi _backupApi = locator<BackupCurrencyApi>();
@override
CurrencyPair getConversionPair() {
@@ -17,4 +28,20 @@ class RepositoryImpl extends Repository {
return _prefs.saveConversionPair(fromCurrency, toCurrency);
}
@override
Future<Currency> getConversationRateFromApi(String fromCurrency, String toCurrency) async {
String from = fromCurrency.getCurrencyCode();
String to = toCurrency.getCurrencyCode();
String currency = "${from}_$to";
try {
ResponseObject responseObject = await getDataFromApiCall(_api.getConversion(dotenv.env['apiKey']!, currency));
return responseObject.convert();
} on HttpException catch(error) {
logger.e(error);
CurrencyResponse responseObject = await getDataFromApiCall(_backupApi.getCurrencyRate(from, to));
return responseObject.convert();
}
}
}

View File

@@ -1,4 +1,7 @@
import 'package:dio/dio.dart';
import 'package:easy_cc_flutter/MainViewModel.dart';
import 'package:easy_cc_flutter/data/network/backupCurrencyApi.dart';
import 'package:easy_cc_flutter/data/network/currencyApi.dart';
import 'package:easy_cc_flutter/data/repository/RepositoryImpl.dart';
import 'package:get_it/get_it.dart';
@@ -7,8 +10,11 @@ import 'data/prefs/PreferenceProvider.dart';
GetIt locator = GetIt.instance;
void setupLocator() {
final dio = Dio();
locator.registerLazySingleton(() => PreferenceProvider());
locator.registerLazySingleton(() => CurrencyApi(dio));
locator.registerLazySingleton(() => BackupCurrencyApi(dio));
locator.registerLazySingleton(() => RepositoryImpl());
locator.registerFactory(() => MainViewModel());
}

View File

@@ -1,12 +1,19 @@
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:logger/logger.dart';
import 'Home.dart';
import 'data/prefs/PreferenceProvider.dart';
import 'locator.dart';
var logger = Logger(
printer: PrettyPrinter(),
);
void main() async {
WidgetsFlutterBinding.ensureInitialized();
setupLocator();
await dotenv.load();
await locator<PreferenceProvider>().init();
runApp(const MyApp());
}

View File

@@ -1,6 +1,27 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_fe_analyzer_shared:
dependency: transitive
description:
name: _fe_analyzer_shared
url: "https://pub.dartlang.org"
source: hosted
version: "47.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
url: "https://pub.dartlang.org"
source: hosted
version: "4.7.0"
args:
dependency: transitive
description:
name: args
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.1"
async:
dependency: transitive
description:
@@ -15,6 +36,62 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
build:
dependency: transitive
description:
name: build
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.1"
build_config:
dependency: transitive
description:
name: build_config
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
build_daemon:
dependency: transitive
description:
name: build_daemon
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.0"
build_resolvers:
dependency: transitive
description:
name: build_resolvers
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.10"
build_runner:
dependency: "direct dev"
description:
name: build_runner
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.1"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
url: "https://pub.dartlang.org"
source: hosted
version: "7.2.4"
built_collection:
dependency: transitive
description:
name: built_collection
url: "https://pub.dartlang.org"
source: hosted
version: "5.1.1"
built_value:
dependency: transitive
description:
name: built_value
url: "https://pub.dartlang.org"
source: hosted
version: "8.4.1"
characters:
dependency: transitive
description:
@@ -22,6 +99,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.1"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
clock:
dependency: transitive
description:
@@ -29,6 +113,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.1"
code_builder:
dependency: transitive
description:
name: code_builder
url: "https://pub.dartlang.org"
source: hosted
version: "4.3.0"
collection:
dependency: transitive
description:
@@ -36,6 +127,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.16.0"
convert:
dependency: transitive
description:
name: convert
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.2"
crypto:
dependency: transitive
description:
@@ -50,6 +148,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.5"
dart_style:
dependency: transitive
description:
name: dart_style
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.4"
dio:
dependency: transitive
description:
name: dio
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.6"
dropdown_search:
dependency: "direct main"
description:
@@ -85,11 +197,25 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "6.1.4"
fixnum:
dependency: transitive
description:
name: fixnum
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_dotenv:
dependency: "direct main"
description:
name: flutter_dotenv
url: "https://pub.dartlang.org"
source: hosted
version: "5.0.2"
flutter_lints:
dependency: "direct dev"
description:
@@ -107,6 +233,13 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
frontend_server_client:
dependency: transitive
description:
name: frontend_server_client
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.3"
get:
dependency: transitive
description:
@@ -121,6 +254,41 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "7.2.0"
glob:
dependency: transitive
description:
name: glob
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
graphs:
dependency: transitive
description:
name: graphs
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
http_multi_server:
dependency: transitive
description:
name: http_multi_server
url: "https://pub.dartlang.org"
source: hosted
version: "3.2.1"
http_parser:
dependency: transitive
description:
name: http_parser
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.1"
io:
dependency: transitive
description:
name: io
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
js:
dependency: transitive
description:
@@ -128,6 +296,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.4"
json_annotation:
dependency: transitive
description:
name: json_annotation
url: "https://pub.dartlang.org"
source: hosted
version: "4.6.0"
json_serializable:
dependency: "direct dev"
description:
name: json_serializable
url: "https://pub.dartlang.org"
source: hosted
version: "6.3.2"
lazy_evaluation:
dependency: "direct main"
description:
@@ -142,6 +324,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
logger:
dependency: "direct main"
description:
name: logger
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
logging:
dependency: transitive
description:
name: logging
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
matcher:
dependency: transitive
description:
@@ -163,6 +359,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
mime:
dependency: transitive
description:
name: mime
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
nested:
dependency: transitive
description:
@@ -170,6 +373,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
package_config:
dependency: transitive
description:
name: package_config
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
path:
dependency: transitive
description:
@@ -212,6 +422,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.2"
pool:
dependency: transitive
description:
name: pool
url: "https://pub.dartlang.org"
source: hosted
version: "1.5.1"
process:
dependency: transitive
description:
@@ -226,6 +443,41 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "6.0.3"
pub_semver:
dependency: transitive
description:
name: pub_semver
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
pubspec_parse:
dependency: transitive
description:
name: pubspec_parse
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.1"
quiver:
dependency: transitive
description:
name: quiver
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.0"
retrofit:
dependency: "direct main"
description:
name: retrofit
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1+1"
retrofit_generator:
dependency: "direct dev"
description:
name: retrofit_generator
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.3+2"
sealed_annotations:
dependency: "direct main"
description:
@@ -289,11 +541,39 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
shelf:
dependency: transitive
description:
name: shelf
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.2"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_gen:
dependency: transitive
description:
name: source_gen
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.3"
source_helper:
dependency: transitive
description:
name: source_helper
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.3"
source_span:
dependency: transitive
description:
@@ -336,6 +616,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
stream_transform:
dependency: transitive
description:
name: stream_transform
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
string_scanner:
dependency: transitive
description:
@@ -357,6 +644,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.13"
timing:
dependency: transitive
description:
name: timing
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
toast:
dependency: "direct main"
description:
@@ -364,6 +658,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.0"
tuple:
dependency: transitive
description:
name: tuple
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
typed_data:
dependency: transitive
description:
@@ -392,6 +693,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.3"
watcher:
dependency: transitive
description:
name: watcher
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.0"
win32:
dependency: transitive
description:
@@ -406,6 +721,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0+2"
yaml:
dependency: transitive
description:
name: yaml
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.1"
sdks:
dart: ">=2.18.0-190.0.dev <3.0.0"
flutter: ">=3.0.0"

View File

@@ -44,11 +44,17 @@ dependencies:
cupertino_icons: ^1.0.2
shared_preferences: ^2.0.15
get_it: ^7.2.0
retrofit: ^3.0.1+1
logger: ^1.1.0
flutter_dotenv: ^5.0.2
dev_dependencies:
flutter_test:
sdk: flutter
retrofit_generator: '>=4.0.0 <5.0.0'
build_runner: '>=2.2.1 <4.0.0'
json_serializable: '>4.4.0'
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
@@ -68,7 +74,8 @@ flutter:
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
assets:
- .env
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg