Mokito wont return a stubbed value - flutter

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

Unhandled Exception: Invalid argument: is a regular instance: Instance of 'LocationDto'

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.

displaying only the current user data

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.

Flutter Riverpod : How to Implement FutureProvider?

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.

Mock getExternalStorageDirectory on Flutter

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.

Flutter: Shared preferences singleton not initializing properly

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);
}