mirror of
https://github.com/hmalik144/easy_cc_flutter.git
synced 2025-12-10 03:05:34 +00:00
Removal of api key
Network calls, integration of retrofit api added backup api
This commit is contained in:
@@ -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() {}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
6
lib/Utils/currencyUtils.dart
Normal file
6
lib/Utils/currencyUtils.dart
Normal file
@@ -0,0 +1,6 @@
|
||||
extension CurrencyExtension on String {
|
||||
|
||||
String getCurrencyCode(){
|
||||
return substring(0,3);
|
||||
}
|
||||
}
|
||||
11
lib/data/model/Currency.dart
Normal file
11
lib/data/model/Currency.dart
Normal 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();
|
||||
}
|
||||
35
lib/data/network/backupCurrencyApi.dart
Normal file
35
lib/data/network/backupCurrencyApi.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
54
lib/data/network/currencyApi.dart
Normal file
54
lib/data/network/currencyApi.dart
Normal 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);
|
||||
}
|
||||
30
lib/data/network/safeApiCall.dart
Normal file
30
lib/data/network/safeApiCall.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'CurrencyPair.dart';
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user