I started TDD recently and it has slowed my progress, I m trying to stub a return value with Mockito, but i keep getting "null is not a subtype of Future-dynamic' not sure what i did wrong, here is my setup
class ImageSelectorMock extends Mock implements ImageSelector {}
getImageSelectorMock({logo}) {
_unregisterIfRegistered<ImageSelector>();
var imageSelector = ImageSelectorMock();
if (logo != null) {
when(imageSelector.businessLogo).thenReturn('string');
when(imageSelector.uploadImage(null, 'folder'))
.thenAnswer((realInvocation) async =>
Future.value('send this')
);
}
locator.registerSingleton<ImageSelector>(imageSelector);
return imageSelector;
}
setupService() {
//getAuthServiceMock();
//getFirestoreServiceMock();
//getNavigationServiceMock();
getImageSelectorMock();
}
void _unregisterIfRegistered<T extends Object>() {
if (locator.isRegistered<T>()) {
locator.unregister<T>();
}
}
getImageSelectorMock takes an optional arg "logo" to check if imageSelector.uploadImage is called. Also, I'm using a locator/get_it package
group('RegistrationviewmodelTest -', () {
setUp(() => {setupService()});
test('when business logo is NOT null => uplaodImage should be called', () async {
// final navigation = getNavigationServiceMock();
// final fireStore = getFirestoreServiceMock();
final imageSelector = await getImageSelectorMock(logo: "test");
final model = RegistraionViewModel();
await model.createBusiness({
'title': "text",
'description': "text",
});
verify(imageSelector.uploadImage('', 'upload'));
// verify(navigation.navigateTo());
});
});
// uploadImage does
Future uploadImage(file, folder) async {
// String fileName = basename(_imageFile.path);
String fileName = basename(file.path);
final firebaseStorageRef =
FirebaseStorage.instance.ref().child('$folder/$fileName');
final uploadTask = firebaseStorageRef.putFile(file);
final taskSnapshot = await uploadTask;
return taskSnapshot.ref.getDownloadURL().then((value) => value);
}
uploadImage is just an image picker that returns download Url from fire storege.
Thanks for helping out!!
Related
currently I am us this plugin to get the background location of a user, works great with ios but fails on android after recent updates and the plugin progress stalled, I am now faced with the issue below
so, in my main class i have this
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
// onLineStatus = UserSimplePreferences.getUsername() ?? ''
onLineStatus = UserSimplePrefences.getButtonStatus() ?? false;
displayToastMessage(onLineStatus.toString(), context);
osmController = MapController(
initMapWithUserPosition: true,
//initPosition: initPosition,
);
osmController.addObserver(this);
scaffoldKey = GlobalKey<ScaffoldState>();
if (IsolateNameServer.lookupPortByName(
LocationServiceRepository.isolateName) !=
null) {
IsolateNameServer.removePortNameMapping(
LocationServiceRepository.isolateName);
}
IsolateNameServer.registerPortWithName(
port.sendPort, LocationServiceRepository.isolateName);
port.listen(
(dynamic data) async {
if (data != null) await updateUI(data);
},
);
initPlatformState();
}
Future<void> updateUI(LocationDto data) async {
await _updateNotificationText(data);
}
Future<void> _updateNotificationText(LocationDto data) async {
await BackgroundLocator.updateNotificationText(
title: "Your location is updated",
msg: "${DateTime.now()}",
bigMsg: "${data.latitude}, ${data.longitude}");
}
Future<void> initPlatformState() async {
print('Initializing...');
await BackgroundLocator.initialize();
await BackgroundLocator.isServiceRunning();
}
Future<void> _startLocator() async {
Map<String, dynamic> data = {'countInit': 1};
return await BackgroundLocator.registerLocationUpdate(
LocationCallbackHandler.callback,
initCallback: LocationCallbackHandler.initCallback,
initDataCallback: data,
disposeCallback: LocationCallbackHandler.disposeCallback,
iosSettings: IOSSettings(
accuracy: LocationAccuracy.NAVIGATION,
distanceFilter: 0,
stopWithTerminate: true),
autoStop: false,
androidSettings: AndroidSettings(
accuracy: LocationAccuracy.NAVIGATION,
interval: 5,
distanceFilter: 0,
client: LocationClient.google,
androidNotificationSettings: AndroidNotificationSettings(
notificationChannelName: 'Location tracking',
notificationTitle: 'Start Location Tracking',
notificationMsg: 'Track location in background',
notificationBigMsg:
'Background location is on to keep the app up-tp-date with your location. This is required for main features to work properly when the app is not running.',
notificationIconColor: Colors.grey,
notificationTapCallback:
LocationCallbackHandler.notificationCallback)));
}
This is the LocationRepositoryClass
class LocationServiceRepository {
static LocationServiceRepository _instance = LocationServiceRepository._();
LocationServiceRepository._();
factory LocationServiceRepository() {
return _instance;
}
static const String isolateName = 'LocatorIsolate';
int _count = -1;
Future<void> init(Map<dynamic, dynamic> params) async {
//TODO change logs
print("***********Init callback handler");
if (params.containsKey('countInit')) {
dynamic tmpCount = params['countInit'];
if (tmpCount is double) {
_count = tmpCount.toInt();
} else if (tmpCount is String) {
_count = int.parse(tmpCount);
} else if (tmpCount is int) {
_count = tmpCount;
} else {
_count = -2;
}
} else {
_count = 0;
}
print("$_count");
await setLogLabel("start");
final SendPort? send = IsolateNameServer.lookupPortByName(isolateName);
send?.send(null);
}
Future<void> dispose() async {
print("***********Dispose callback handler");
print("$_count");
await setLogLabel("end");
final SendPort? send = IsolateNameServer.lookupPortByName(isolateName);
send?.send(null);
}
#pragma('vm:entry-point')
Future<void> callback(LocationDto locationDto) async {
print('$_count location in dart: ${locationDto.toString()}');
await setLogPosition(_count, locationDto);
final SendPort? send = IsolateNameServer.lookupPortByName(isolateName);
send?.send(locationDto);//error here
_count++;
}
static Future<void> setLogLabel(String label) async {
final date = DateTime.now();
// await FileManager.writeToLogFile(
// '------------\n$label: ${formatDateLog(date)}\n------------\n');
}
static Future<void> setLogPosition(int count, LocationDto data) async {
final date = DateTime.now();
// await FileManager.writeToLogFile(
// '$count : ${formatDateLog(date)} --> ${formatLog(data)} --- isMocked: ${data.isMocked}\n');
}
static double dp(double val, int places) {
num mod = pow(10.0, places);
return ((val * mod).round().toDouble() / mod);
}
static String formatDateLog(DateTime date) {
return date.hour.toString() +
":" +
date.minute.toString() +
":" +
date.second.toString();
}
static String formatLog(LocationDto locationDto) {
return dp(locationDto.latitude, 4).toString() +
" " +
dp(locationDto.longitude, 4).toString();
}
}
and this is the locationCallbackHandler class
class LocationCallbackHandler {
static Future<void> initCallback(Map<dynamic, dynamic> params) async {
LocationServiceRepository myLocationCallbackRepository =
LocationServiceRepository();
await myLocationCallbackRepository.init(params);
}
static Future<void> disposeCallback() async {
LocationServiceRepository myLocationCallbackRepository =
LocationServiceRepository();
await myLocationCallbackRepository.dispose();
}
#pragma('vm:entry-point')
static void callback(LocationDto locationDto) async {
LocationServiceRepository myLocationCallbackRepository =
LocationServiceRepository();
await myLocationCallbackRepository.callback(locationDto);
}
static Future<void> notificationCallback() async {
print('***notificationCallback');
}
}
Everthing was working fine until this update with flutter and kotlin, Now i have the error
[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Invalid argument: is a regular instance: Instance of 'LocationDto'
E/flutter (26006): #0 _SendPort._sendInternal (dart:isolate-patch/isolate_patch.dart:249:43)
E/flutter (26006): #1 _SendPort.send (dart:isolate-patch/isolate_patch.dart:230:5)
E/flutter (26006): #2 LocationServiceRepository.callback
package:drivers_app/tabPages/location_service_reposirtory.dart:59
The error points to this code
#pragma('vm:entry-point')
Future<void> callback(LocationDto locationDto) async {
print('$_count location in dart: ${locationDto.toString()}');
await setLogPosition(_count, locationDto);
final SendPort? send = IsolateNameServer.lookupPortByName(isolateName);
send?.send(locationDto); //error here
_count++;
}
There is no reply from the plugin developer, Any help would be appreciated.
https://github.com/Yukams/background_locator_fixed/pull/53
There were some errors after updating flutter to version 3, If you run into this error then please visit the above website for the fix.
I protected data_service with current user to only display the current user's habits.
data_service.dart:
class DataService {...
late final Database db;
Users? _user;
late final StreamData<Map<int, Habit>> habits;
Future<void> init() async {
db = await HabitsDb.connectToDb();
habits = StreamData(initialValue: await _getAllHabits(), broadcast: true);
}
String get userEmail => AuthService.firebase().currentUser!.email;
Future<Map<int, Habit>> _getAllHabits() async {
getOrCreateUser(email: userEmail); //issue
final habits = await _getAllHabitsFromDb();
final map = Map<int, Habit>();
final currentUser = _user;
print(currentUser);
for (final habit in habits) {
if (currentUser != null) {
print(currentUser.id);
print(habit.userId);
if (habit.userId == currentUser.id) {
map[habit.id] = habit;
}
}
//map[habit.userId] = currentUser?.id;
}
return map;
}
Future<List<Habit>> _getAllHabitsFromDb() async {
final habitsMap = await HabitsDb.getAllHabits(db);
final habitsList = habitsMap.map((e) => Habit.fromDb(e)).toList();
return habitsList;
}
Future<Users> getOrCreateUser({
required String email,
bool setAsCurrentUser = true,
}) async {
try {
//we found the user
final user = await getUser(email: email);
if (setAsCurrentUser) {
_user = user;
}
print(_user?.email);
return user;
} on CouldNotFindUser {
//we didn't find the user
final createdUser = await createUser(email: email);
if (setAsCurrentUser) {
_user = createdUser;
}
return createdUser;
} catch (e) {
rethrow;
}
}
...}
in main class:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
final dataService = DataService();
await dataService.init();
GetIt.I.registerSingleton(dataService);
... }
StreamData class:
class StreamData<T> {
List<Habit> _notes = [];
User? _user;
late final StreamController<T> _controller;
Stream<T> get stream => _controller.stream;
late T _value;
T get value => _value;
StreamData({required T initialValue, bool broadcast = true}) {
if (broadcast) {
_controller = StreamController<T>.broadcast();
} else {
_controller = StreamController<T>();
}
_value = initialValue;
}
the problem is that the line getOrCreateUser(email: userEmail); is only called once and it does not work when I switch user and I need to Hot Restart to fix it. I think using Futurebuilder will fix it. but if yes, how do I use it when there is a need to call dataService.init at the beginning of the main?
Since your getOrCreateUser function is declared as async, you'll want to use await when you call it in _getAllHabits:
await getOrCreateUser(email: userEmail)
This ensures the getOrCreateUser code has completed before the rest of the code in _getAllHabits (that depends on the result of getOrCreateUser) executes.
I using Flutter Riverpod package to handling http request. I have simple Http get request to show all user from server, and i using manage it using FutureProvider from Flutter Riverpod package.
API
class UserGoogleApi {
Future<List<UserGoogleModel>> getAllUser() async {
final result = await reusableRequestServer.requestServer(() async {
final response =
await http.get('${appConfig.baseApiUrl}/${appConfig.userGoogleController}/getAllUser');
final Map<String, dynamic> responseJson = json.decode(response.body);
if (responseJson['status'] == 'ok') {
final List list = responseJson['data'];
final listUser = list.map((e) => UserGoogleModel.fromJson(e)).toList();
return listUser;
} else {
throw responseJson['message'];
}
});
return result;
}
}
User Provider
class UserProvider extends StateNotifier<UserGoogleModel> {
UserProvider([UserGoogleModel state]) : super(UserGoogleModel());
Future<UserGoogleModel> searchUserByIdOrEmail({
String idUser,
String emailuser,
String idOrEmail = 'email_user',
}) async {
final result = await _userGoogleApi.getUserByIdOrEmail(
idUser: idUser,
emailUser: emailuser,
idOrEmail: idOrEmail,
);
UserGoogleModel temp;
for (var item in result) {
temp = item;
}
state = UserGoogleModel(
idUser: temp.idUser,
createdDate: temp.createdDate,
emailUser: temp.emailUser,
imageUser: temp.emailUser,
nameUser: temp.nameUser,
tokenFcm: temp.tokenFcm,
listUser: state.listUser,
);
return temp;
}
Future<List<UserGoogleModel>> showAllUser() async {
final result = await _userGoogleApi.getAllUser();
state.listUser = result;
return result;
}
}
final userProvider = StateNotifierProvider((ref) => UserProvider());
final showAllUser = FutureProvider.autoDispose((ref) async {
final usrProvider = ref.read(userProvider);
final result = await usrProvider.showAllUser();
return result;
});
After that setup, i simply can call showAllUser like this :
Consumer((ctx, read) {
final provider = read(showAllUser);
return provider.when(
data: (value) {
return ListView.builder(
itemCount: value.length,
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
final result = value[index];
return Text(result.nameUser);
},
);
},
loading: () => const CircularProgressIndicator(),
error: (error, stackTrace) => Text('Error $error'),
);
}),
it's no problem if http request don't have required parameter, but i got problem if my http request required parameter. I don't know how to handle this.
Let's say , i have another http get to show specific user from id user or email user. Then API look like :
API
Future<List<UserGoogleModel>> getUserByIdOrEmail({
#required String idUser,
#required String emailUser,
#required String idOrEmail,
}) async {
final result = await reusableRequestServer.requestServer(() async {
final baseUrl =
'${appConfig.baseApiUrl}/${appConfig.userGoogleController}/getUserByIdOrEmail';
final chooseURL = idOrEmail == 'id_user'
? '$baseUrl?id_or_email=$idOrEmail&id_user=$idUser'
: '$baseUrl?id_or_email=$idOrEmail&email_user=$emailUser';
final response = await http.get(chooseURL);
final Map<String, dynamic> responseJson = json.decode(response.body);
if (responseJson['status'] == 'ok') {
final List list = responseJson['data'];
final listUser = list.map((e) => UserGoogleModel.fromJson(e)).toList();
return listUser;
} else {
throw responseJson['message'];
}
});
return result;
}
User Provider
final showSpecificUser = FutureProvider.autoDispose((ref) async {
final usrProvider = ref.read(userProvider);
final result = await usrProvider.searchUserByIdOrEmail(
idOrEmail: 'id_user',
idUser: usrProvider.state.idUser, // => warning on "state"
);
return result;
});
When i access idUser from userProvider using usrProvider.state.idUser , i got this warning.
The member 'state' can only be used within instance members of subclasses of 'package:state_notifier/state_notifier.dart'.
It's similiar problem with my question on this, but on that problem i already know to solved using read(userProvider.state) , but in FutureProvider i can't achieved same result using ref(userProvider).
I missed something ?
Warning: This is not a long-term solution
Assuming that your FutureProvider is being properly disposed after each use that should be a suitable workaround until the new changes to Riverpod are live. I did a quick test to see and it does work. Make sure you define a getter like this and don't override the default defined by StateNotifier.
class A extends StateNotifier<B> {
...
static final provider = StateNotifierProvider((ref) => A());
getState() => state;
...
}
final provider = FutureProvider.autoDispose((ref) async {
final a = ref.read(A.provider);
final t = a.getState();
print(t);
});
Not ideal but seems like a fine workaround. I believe the intention of state being inaccessible outside is to ensure state manipulations are handled by the StateNotifier itself, so using a getter in the meantime wouldn't be the end of the world.
I´m trying mock the function getExternalStorageDirectory, but alway return the error:
"UnsupportedError (Unsupported operation: Functionality only available on Android)"
I´m using the method setMockMethodCallHandler to mock it, but the erro occurs before the method be called.
test method
test('empty listReportModel', () async {
TestWidgetsFlutterBinding.ensureInitialized();
final directory = await Directory.systemTemp.createTemp();
const MethodChannel channel =
MethodChannel('plugins.flutter.io/path_provider');
channel.setMockMethodCallHandler((MethodCall methodCall) async {
if (methodCall.method == 'getExternalStorageDirectory') {
return directory.path;
}
return ".";
});
when(Modular.get<IDailyGainsController>().listDailyGains())
.thenAnswer((_) => Future.value(listDailyGainsModel));
when(Modular.get<IReportsController>().listReports())
.thenAnswer((_) => Future.value(new List<ReportsModel>()));
var configurationController = Modular.get<IConfigurationController>();
var response = await configurationController.createBackup();
expect(response.filePath, null);
});
method
Future<CreateBackupResponse> createBackup() async {
CreateBackupResponse response = new CreateBackupResponse();
var dailyGains = await exportDailyGainsToCSV();
var reports = await exportReportsToCSV();
final Directory directory = await getApplicationDocumentsDirectory();
final Directory externalDirectory = await getExternalStorageDirectory();
if (dailyGains.filePath != null && reports.filePath != null) {
File dailyGainsFile = File(dailyGains.filePath);
File reportsFile = File(reports.filePath);
var encoder = ZipFileEncoder();
encoder.create(externalDirectory.path + "/" + 'backup.zip');
encoder.addFile(dailyGainsFile);
encoder.addFile(reportsFile);
encoder.close();
await _removeFile(dailyGainsFile.path);
await _removeFile(reportsFile.path);
response.filePath = directory.path + "/" + 'backup.zip';
}
return response;
}
As in pub.dev stated:
path_provider now uses a PlatformInterface, meaning that not all
platforms share the a single PlatformChannel-based implementation.
With that change, tests should be updated to mock PathProviderPlatform
rather than PlatformChannel.
That means somewhere in your test directory you create the following mock class:
import 'package:mockito/mockito.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
const String kTemporaryPath = 'temporaryPath';
const String kApplicationSupportPath = 'applicationSupportPath';
const String kDownloadsPath = 'downloadsPath';
const String kLibraryPath = 'libraryPath';
const String kApplicationDocumentsPath = 'applicationDocumentsPath';
const String kExternalCachePath = 'externalCachePath';
const String kExternalStoragePath = 'externalStoragePath';
class MockPathProviderPlatform extends Mock
with MockPlatformInterfaceMixin
implements PathProviderPlatform {
Future<String> getTemporaryPath() async {
return kTemporaryPath;
}
Future<String> getApplicationSupportPath() async {
return kApplicationSupportPath;
}
Future<String> getLibraryPath() async {
return kLibraryPath;
}
Future<String> getApplicationDocumentsPath() async {
return kApplicationDocumentsPath;
}
Future<String> getExternalStoragePath() async {
return kExternalStoragePath;
}
Future<List<String>> getExternalCachePaths() async {
return <String>[kExternalCachePath];
}
Future<List<String>> getExternalStoragePaths({
StorageDirectory type,
}) async {
return <String>[kExternalStoragePath];
}
Future<String> getDownloadsPath() async {
return kDownloadsPath;
}
}
And build your tests the following way:
void main() {
group('PathProvider', () {
TestWidgetsFlutterBinding.ensureInitialized();
setUp(() async {
PathProviderPlatform.instance = MockPathProviderPlatform();
// This is required because we manually register the Linux path provider when on the Linux platform.
// Will be removed when automatic registration of dart plugins is implemented.
// See this issue https://github.com/flutter/flutter/issues/52267 for details
disablePathProviderPlatformOverride = true;
});
test('getTemporaryDirectory', () async {
Directory result = await getTemporaryDirectory();
expect(result.path, kTemporaryPath);
});
}
}
Here you can check out the complete example.
I'm very new to Flutter and Dart, comming from android, bringing some of my habbits with me, I want to implement a SharedPreferences singleton object to simplify and avoid repetition (duplication).
this is my SharedPreferences singleton class:
import 'package:shared_preferences/shared_preferences.dart';
import 'package:synchronized/synchronized.dart';
class MySharedPreferences {
static MySharedPreferences _instance;
SharedPreferences _preferences;
// keys
final String _logged = "LOGGED";
final String _accessToken = "ACCESS_TOKEN";
MySharedPreferences._() {
_initSharedPreferences();
}
static MySharedPreferences getInstance() {
var lock = new Lock();
if (_instance == null) {
lock.synchronized(() => {_instance = new MySharedPreferences._()});
return _instance;
} else
return _instance;
}
_initSharedPreferences() async {
_preferences = await SharedPreferences.getInstance();
}
bool checkLogged() {
return _preferences.getBool(_logged);
}
void setLogged(bool logged) {
_preferences.setBool(_logged, logged);
}
well most of this logic is what i used to do in android, and used to work perfectly, but when i tried testing it, the singleton is always null, here is the test:
import 'package:flutter_test/flutter_test.dart';
import 'package:reportingsystem/local/my_shared_preferences.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
test('Test the shared_preferences', () {
MySharedPreferences preferences = MySharedPreferences.getInstance();
preferences.setLogged(true);
expect(preferences.checkLogged(), true);
preferences.setLogged(false);
expect(preferences.checkLogged(), false);
});
}
The test fails because the "preferences" object is null, i don't know what wrong, and i don't find much about it in the docs.
here is the stacktrace:
dart:core Object.noSuchMethod
package:reportingsystem/local/my_shared_preferences.dart 34:18 MySharedPreferences.setLogged
test\shared_preferences_test.dart 8:17 main.<fn>
test\shared_preferences_test.dart 6:39 main.<fn>
NoSuchMethodError: The method 'setBool' was called on null.
Receiver: null
Tried calling: setBool("LOGGED", true)
Here's an example where you must call init when first calling the Singleton, and then you'll be able to access it synchronously.
class MySharedPreferences {
static final MySharedPreferences _instance = MySharedPreferences._internal();
MockSharedPreferences prefereces;
factory MySharedPreferences() {
return _instance;
}
Future<void> init() async {
if (prefereces != null) {
return;
}
prefereces = await Future.delayed(Duration(seconds: 1), () => MockSharedPreferences());
}
MySharedPreferences._internal();
}
class MockSharedPreferences {
final Map<String, bool> data = {};
void setBool(String key, bool value) {
data[key] = value;
print('data $data');
}
}
Then you can use it without await after first initialization, like this:
Future<void> main() async {
await first();
anyOther();
}
void anyOther() {
MySharedPreferences singleton = MySharedPreferences();
singleton.prefereces.setBool('first', true);
}
Future<void> first() async {
MySharedPreferences singleton = MySharedPreferences();
await singleton.init();
singleton.prefereces.setBool('notFirst', true);
}