App notification badge update using Firebase Messaging - flutter

I'm using flutter_app_badger and Firebase Messaging to set a notification system which would update number in app badger when a new notification is coming. I'm working in Flutterflow so my approach might be a bit awkward. So far I managed to do two things:
Create a separate function in the app which updates app badger while app is working;
Set the notification system that is triggered by the action in app;
But I can't combine the two. I have the following push_notification_handler code and trying how to put FlutterAppBadger.updateBadgeCount() function it.
import 'dart:async';
import 'dart:convert';
import 'serialization_util.dart';
import '../backend.dart';
import '../../flutter_flow/flutter_flow_theme.dart';
import '../../flutter_flow/flutter_flow_util.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import '../../index.dart';
import '../../main.dart';
final _handledMessageIds = <String?>{};
class PushNotificationsHandler extends StatefulWidget {
const PushNotificationsHandler({Key? key, required this.child})
: super(key: key);
final Widget child;
#override
_PushNotificationsHandlerState createState() =>
_PushNotificationsHandlerState();
}
class _PushNotificationsHandlerState extends State<PushNotificationsHandler> {
bool _loading = false;
Future handleOpenedPushNotification() async {
if (isWeb) {
return;
}
final notification = await FirebaseMessaging.instance.getInitialMessage();
if (notification != null) {
await _handlePushNotification(notification);
}
FirebaseMessaging.onMessageOpenedApp.listen(_handlePushNotification);
}
Future _handlePushNotification(RemoteMessage message) async {
if (_handledMessageIds.contains(message.messageId)) {
return;
}
_handledMessageIds.add(message.messageId);
if (mounted) {
setState(() => _loading = true);
}
try {
final initialPageName = message.data['initialPageName'] as String;
final initialParameterData = getInitialParameterData(message.data);
final pageBuilder = pageBuilderMap[initialPageName];
if (pageBuilder != null) {
final page = await pageBuilder(initialParameterData);
await Navigator.push(
context,
MaterialPageRoute(builder: (context) => page),
);
}
} catch (e) {
print('Error: $e');
} finally {
if (mounted) {
setState(() => _loading = false);
}
}
}
#override
void initState() {
super.initState();
handleOpenedPushNotification();
}
#override
Widget build(BuildContext context) => _loading
? Container(
color: FlutterFlowTheme.of(context).primaryBtnText,
child: Image.asset(
'assets/images/Screen_Shot_2022-11-22_at_12.59.57.png',
fit: BoxFit.contain,
),
)
: widget.child;
}
final pageBuilderMap = <String, Future<Widget> Function(Map<String, dynamic>)>{
'SignInSignUpPage': (data) async => SignInSignUpPageWidget(),
'ForgotPasswordPage': (data) async => ForgotPasswordPageWidget(),
'ContactsListPage': (data) async => ContactsListPageWidget(
tabToOpen: getParameter(data, 'tabToOpen'),
),
'SyncProcessPage': (data) async => SyncProcessPageWidget(),
'SyncConfirmationPage': (data) async => SyncConfirmationPageWidget(),
'ContactDetailsPage': (data) async => ContactDetailsPageWidget(
idReference: getParameter(data, 'idReference'),
comment: getParameter(data, 'comment'),
),
'ContactDeletePage': (data) async => ContactDeletePageWidget(
idReference: getParameter(data, 'idReference'),
comment: getParameter(data, 'comment'),
),
'ContactInvitationPage': (data) async => ContactInvitationPageWidget(
idReference: getParameter(data, 'idReference'),
),
'AddConfirmationAfterInvitePage': (data) async =>
AddConfirmationAfterInvitePageWidget(
uID: getParameter(data, 'uID'),
phraseState: getParameter(data, 'phraseState'),
),
'AddContactsOptionPage': (data) async => AddContactsOptionPageWidget(),
'SearchNewContactsPage': (data) async => SearchNewContactsPageWidget(),
'NewContactInvitationConfirmationPage': (data) async =>
NewContactInvitationConfirmationPageWidget(),
'NewContactScanPage': (data) async => NewContactScanPageWidget(),
'AddConfirmationAfterScanPageNew': (data) async =>
AddConfirmationAfterScanPageNewWidget(
uID: getParameter(data, 'uID'),
phraseState: getParameter(data, 'phraseState'),
),
'AddConfirmationAfterScanPageOld': (data) async =>
AddConfirmationAfterScanPageOldWidget(
uID: getParameter(data, 'uID'),
phraseState: getParameter(data, 'phraseState'),
),
'ShareProfileOptionsPage': (data) async => ShareProfileOptionsPageWidget(),
'InAppQRcodeSharePage': (data) async => InAppQRcodeSharePageWidget(),
'WebQRcodeSharePage': (data) async => WebQRcodeSharePageWidget(),
'ProfileEditPage': (data) async => ProfileEditPageWidget(),
'GameSettingsPage': (data) async => GameSettingsPageWidget(),
'GamePage': (data) async => GamePageWidget(),
'TutorialPage': (data) async => TutorialPageWidget(),
'AboutPage': (data) async => AboutPageWidget(),
'FAQPage': (data) async => FAQPageWidget(),
'PrivacyPolicyPage': (data) async => PrivacyPolicyPageWidget(),
};
bool hasMatchingParameters(Map<String, dynamic> data, Set<String> params) =>
params.any((param) => getParameter(data, param) != null);
Map<String, dynamic> getInitialParameterData(Map<String, dynamic> data) {
try {
final parameterDataStr = data['parameterData'];
if (parameterDataStr == null ||
parameterDataStr is! String ||
parameterDataStr.isEmpty) {
return {};
}
return jsonDecode(parameterDataStr) as Map<String, dynamic>;
} catch (e) {
print('Error parsing parameter data: $e');
return {};
}
}
Would be super grateful for any advice!

Related

FakeUsedError: 'execute' No Stub was found

I'm using mockito for testing, riverpod for state management. I'm trying to test the method in my controller class but getting the FakeUsedError:
FakeUsedError: 'execute' No stub was found which matches the argument
of this method call: execute(Instance of 'AuthUseCaseInput').
I'm calling AuthUseCase class method from the AuthController class.
class AuthController extends StateNotifier<AuthState> {
final AuthUseCase authUseCase;
AuthController(this.authUseCase) : super(const AuthState.initial());
Future<void> mapAuthEventToAuthState(AuthEvent event) async {
state = const AuthState.loading();
await event.map(
signInWithEmailAndPassword: (signInWithEmailAndPassword) async {
final result = await authUseCase.execute(AuthUseCaseInput(
signInWithEmailAndPassword.email,
signInWithEmailAndPassword.password));
await result.fold(
(failure) async => state = AuthState.error(failure),
(login) async => state = const AuthState.loggedIn(),
);
});
}
The test class code is given below
void main() {
late AuthUseCase mockAuthUseCase;
late Login login;
late AuthUseCaseInput authUseCaseInput;
late AuthController authController;
setUpAll(() {
mockAuthUseCase = MockAuthUseCase();
login = LoginModel.fromJson(
json.decode(
jsonReader('helpers/dummy_data/login_success_response.json'),
),
).toEntity();
authUseCaseInput = AuthUseCaseInput(email, password);
when(mockAuthUseCase.execute(authUseCaseInput)).thenAnswer(
(_) async => Right(login),
);
authController = AuthController(mockAuthUseCase);
});
group('Auth Controller', () {
stateNotifierTest<AuthController, AuthState>(
'[AuthState.loggedIn] when sign in is success',
setUp: () async {
when(mockAuthUseCase.execute(authUseCaseInput))
.thenAnswer(
(_) async => Right(login),
);
},
actions: (notifier) => notifier.mapAuthEventToAuthState(
const SignInWithEmailAndPassword(email, password)),
expect: () => [const AuthState.loading(), const AuthState.loggedIn()],
build: () {
return authController;
});
});
}

Flutter autoDispose riverpod StateNotifierProvider

This is my shared riverpod class that i want to use that on multiple screens, but after navigate to another screen using ref.listen couldn't dispose or cancel and using another ref.listen work twice, how can i cancel each ref.listen on screen which i used that? for example you suppose i have two screen A and B and into A screen i have
A screen
final future = ref.watch(requestProvider);
ref.listen<NetworkRequestState<int?>>(requestProvider, (
NetworkRequestState? previousState,
NetworkRequestState newState,
) {});
on this ref.listen i navigate to another screen when server return 200 ok? now in B screen which i have ref.listen again:
B screen
final future = ref.watch(requestProvider);
ref.listen<NetworkRequestState<int?>>(requestProvider, (
NetworkRequestState? previousState,
NetworkRequestState newState,
) {});
without sending any request to server this listener work and listen to previous listener
requestProvider on this class shared between multiple screens and autoDispose don't work for that, because after creating another StateNotifierProvider such as requestProviderA_Screen work fine without problem, for example:
final requestProvider = StateNotifierProvider.autoDispose<RequestNotifier,
NetworkRequestState<int?>>(
(ref) => RequestNotifier(ref.watch(requestRepositoryProvider)));
final requestProviderA_Screen = StateNotifierProvider.autoDispose<RequestNotifier,
NetworkRequestState<int?>>(
(ref) => RequestNotifier(ref.watch(requestRepositoryProvider)));
my request riverpod class:
final requestRepositoryProvider =
Provider.autoDispose<Repository>((ref) => Repository(ref.read));
final requestProvider = StateNotifierProvider.autoDispose<RequestNotifier,
NetworkRequestState<int?>>(
(ref) => RequestNotifier(ref.watch(requestRepositoryProvider)));
class Repository {
final Reader _reader;
Repository(this._reader);
Future<int?> getResponse(
HTTP method, String endPoint, Map<String, dynamic> parameters) async {
try {
const r = RetryOptions(maxAttempts: 3);
final response = await r.retry(
() => _submit(method, endPoint, parameters),
retryIf: (e) => e is SocketException || e is TimeoutException,
);
return response.statusCode;
} on DioError catch (e) {
throw (e.response != null
? e.response!.statusCode
: e.error.osError.errorCode) as Object;
}
}
Future<Response> _submit(
HTTP method, String endPoint, Map<String, dynamic> parameters) {
final Options options = Options(
headers: {'Content-Type': 'application/json'},
);
late Future<Response> _r;
switch (method) {
case HTTP.GET:
_r = _reader(dioProvider).get(
endPoint,
queryParameters: parameters,
options: options,
);
break;
case HTTP.POST:
_r = _reader(dioProvider).post(
endPoint,
queryParameters: parameters,
options: options,
);
break;
}
return _r.timeout(const Duration(seconds: 30));
}
}
class RequestNotifier extends RequestStateNotifier<int?> {
final Repository _repository;
RequestNotifier(this._repository);
Future<NetworkRequestState<int?>> send({
required HTTP method,
required String endPoint,
required Map<String, dynamic> parameters,
}) =>
makeRequest(
() => _repository.getResponse(method, endPoint, parameters));
}
and one of screen which i use this class:
class SignUp extends HookConsumerWidget {
final String mobileNumber;
const SignUp({Key? key, required this.mobileNumber}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
final _formKey = useMemoized(() => GlobalKey<FormState>());
final _nameFamily = useTextEditingController();
final future = ref.watch(requestProvider);
useEffect(() {
_nameFamily.dispose();
}, [_nameFamily]);
ref.listen<NetworkRequestState<int?>>(requestProvider, (
NetworkRequestState? previousState,
NetworkRequestState newState,
) {
newState.when(
idle: () {},
//...
}
success: (status) {
//...
Routes.seafarer.navigate(
'/complete-register',
params: {
'mobile_number': mobileNumber.trim(),
'name_family': _nameFamily.text.trim()
},
);
},
error: (error, stackTrace) {
//...
});
});
final _onSubmit = useMemoized(
() => () {
if (_nameFamily.text.trim().isEmpty) {
//...
} else {
//..
ref.read(requestProvider.notifier).send(
method: HTTP.GET,
endPoint: Server.$updateNameFamily,
parameters: {
'mobile_number': mobileNumber,
'name_family': _nameFamily.text.trim()
});
}
},
[_formKey],
);
return Scaffold(
//...
);
}
}

type 'Null' is not a subtype of type 'Future<bool>'

I'm getting the below error while I'm trying to implement bloc testing in my flutter project
type 'Null' is not a subtype of type 'Future<bool>'
package:mynovatium/features/signup/repositories/signup_repository.dart 10:16 MockRepository.createAccountsignup
Following are the corresponding files that might help identify the cause of the error
signup_bloc_test.dart
class MockRepository extends Mock implements SignUpRepository {}
void main() async {
await configureInjection(inj.Environment.test);
group('SignupBloc', () {
late SignUpBloc signUpBloc;
late SignUpRepository signupRepositoryMock;
setUp(() {
signupRepositoryMock = MockRepository();
signUpBloc = SignUpBloc(signUpRepository: signupRepositoryMock);
});
test('initial state of the bloc is [AuthenticationInitial]', () {
expect(SignUpBloc(signUpRepository: signupRepositoryMock).state,
SignupInitial(),);
});
group('SignUpCreateAccount', () {
blocTest<SignUpBloc, SignUpState>(
'emits [SignUpCreateAccountLoading, SignupInitial] '
'state when successfully Signed up',
setUp: () {
when(signupRepositoryMock.createAccount(
'Nevil',
'abcd',
'nikunj#gmail.com',
'english',
),).thenAnswer((_) async => Future<bool>.value(true));
},
build: () => SignUpBloc(signUpRepository: signupRepositoryMock),
act: (SignUpBloc bloc) => bloc.add(
const SignUpCreateAccount(
'Nevil',
'abcd',
'nikunj#gmail.com',
'english',
),
),
expect: () => [
SignUpCreateAccountLoading(),
SignupInitial(),
],
);
});
});
}
signup_repository.dart
This is the code for the signup repository.
class SignUpRepository {
Future<bool> createAccount(String _firstName, String _lastName, String _eMailAddress, String _language) async {
final Response _response;
try {
_response = await CEApiRequest().post(
Endpoints.createCustomerAPI,
jsonData: <String, dynamic>{
'firstName': _firstName,
'lastName': _lastName,
'email': _eMailAddress,
'language': _language,
'responseUrl': Endpoints.flutterAddress,
},
);
final Map<String, dynamic> _customerMap = jsonDecode(_response.body);
final CustomerModel _clients = CustomerModel.fromJson(_customerMap['data']);
if (_clients.id != null) {
return true;
} else {
return false;
}
} on KBMException catch (e) {
final KBMException _exception = e;
throw _exception;
}
}
}
If anyone has any ideas on what might be the issue here, please help!!
Okay so in the above code you need to stub the methods within the mock repository as well and override it to have it return something incase null is being returned.
class MockRepository extends Mock implements SignUpRepository {
#override
Future<bool> createAccount(String? _firstName, String? _lastName, String? _eMailAddress, String? _language) =>
super.noSuchMethod(Invocation.method(#createAccount, [_firstName, _lastName, _eMailAddress, _language]),
returnValue: Future<bool>.value(false),);
}
Doing something like that done in the above code works well.

Flutter socket io not establishing connection after logging in the app

so I'm having this really weird bug connecting to the backend ws server.
flutter 2.10.3
dart 2.16.1
socket_io_client ^2.0.0-beta.4-nullsafety.0
If I start the app, and go through the login process, it doesn't connect, doesn't show any error, or any event. there's nothing at all. (but it sometimes works, it's totally random but let's say it does 1/10 times)
And if I reload the app, it connects successfully. also when I add breakpoints in the code, it works fine.
// ignore_for_file: avoid_print, cascade_invocations
import 'package:bloc/bloc.dart';
import 'package:dealize/core/helpers/flutter_secure_storage_helper.dart';
import 'package:dealize/features/chat/data/models/channel_model.dart';
import 'package:dealize/features/chat/data/models/message_model.dart';
import 'package:dealize/features/chat/domain/entities/channel.dart';
import 'package:dealize/features/chat/domain/entities/message.dart';
import 'package:equatable/equatable.dart';
import 'package:socket_io_client/socket_io_client.dart' as io;
part 'chat_event.dart';
part 'chat_state.dart';
class ChatBloc extends Bloc<ChatEvent, ChatState> {
ChatBloc({
required this.socketUrl,
required this.secureStorageHelper,
}) : super(
const ChatState(
channels: <Channel>[],
messages: <Message?>[],
),
) {
on<Connect>(
(event, emit) async {
try {
final accessToken = await secureStorageHelper.read('accessToken');
_socket = io.io(
socketUrl,
io.OptionBuilder()
.setTransports(['websocket'])
.setAuth(
<String, dynamic>{
'token': accessToken,
},
)
.disableAutoConnect()
.build(),
);
_socket
..onAny((event, dynamic data) {
print('EVENT ===> $event');
print('DATA ===> $data');
})
..onDisconnect((dynamic data) {
print('Disconnected: $data');
})
..on('channel', (dynamic data) async {
final channel =
ChannelModel.fromJson(data as Map<String, dynamic>);
add(AddedChannel(channel: channel));
})
..on('participant', (dynamic data) async {
print('Participant ===> $data');
// todo find participant and update it
// todo add updatedChannel event
})
..on('message', (dynamic data) async {
final message =
MessageModel.fromJson(data as Map<String, dynamic>);
add(AddedMessage(message: message));
add(MessageDelivered());
})
..on('exception', (dynamic data) async {
print('Error ===> $data');
// todo show toast message
});
_socket.connect();
} catch (e) {
print(e.toString());
}
},
);
on<AddedChannel>(
(event, emit) =>
emit(state.copyWith(channels: [...state.channels, event.channel])),
);
on<AddedMessage>(
(event, emit) {
final currentChannelIndex = state.channels.indexWhere(
(channel) => channel.id == event.message.channel,
);
final currentChannel = state.channels.removeAt(currentChannelIndex);
final messageIndex = currentChannel.messages
.indexWhere((element) => element!.id == event.message.id);
if (messageIndex == -1) {
currentChannel.messages.add(event.message);
} else {
currentChannel.messages[messageIndex] = event.message;
}
emit(
state.copyWith(
channels: [...state.channels, currentChannel],
),
);
},
);
on<CreateChannel>((event, emit) {
final phone = '+${state.dialCode}${state.phone}';
_socket.emit('create_channel', {
'phone': phone,
});
});
on<CreateMessage>((event, emit) {
_socket.emit('create_message', {
'channelId': event.channelId,
'type': event.type.name,
'text': state.newMessage,
});
});
on<MessageDelivered>((event, emit) {
_socket.emit('message_delivered', {
'': '',
});
});
on<MessageRead>((event, emit) {
_socket.emit('message_read', {
'': '',
});
});
on<PhoneNumberUpdated>(
(event, emit) => emit(state.copyWith(phone: event.phone)),
);
on<DialCodeUpdated>(
(event, emit) => emit(state.copyWith(dialCode: event.dialCode)),
);
on<NewMessageUpdated>(
(event, emit) => emit(state.copyWith(newMessage: event.message)),
);
on<EnterChannel>((event, emit) {
emit(
state.copyWith(
channelId: event.channelId,
messages: state.channels
.firstWhere((element) => element.id == event.channelId)
.messages,
),
);
add(MessageRead());
});
on<ExitChannel>(
(event, emit) => emit(
state.copyWith(
channelId: '',
messages: [],
),
),
);
}
final String socketUrl;
final FlutterSecureStorageHelper secureStorageHelper;
late final io.Socket _socket;
}
If anyone has an idea it would be really helpful, or a better way to implement socket io connection in flutter through BLoC

How to access future content

I have this function that is working inside a future builder:
Future<Data> fetchData(String barCode, String url) async {
final response = await http.get(Uri.parse(url + barCode));
Map<String, dynamic> novoJson = json.decode(utf8.decode(response.bodyBytes));
novoJson.forEach((key, value) {
if (value == null) {
novoJson.update(key, (value) => "Não encontrado");
}
});
if (response.statusCode == 200) {
return Data.fromJson(novoJson);
} else {
throw Exception('Failed to load album');
}
}
class Data {
final Map<String, dynamic> response;
Data({required this.response});
factory Data.fromJson(Map<String, dynamic> json) {
return Data(response: json);
}
}
Now i'm trying to access the json it returns inside a ElevatedButton, like this:
onPressed: () {
if (_formKey.currentState!.validate()) {
var futureData = fetchData(myController.text, args.url);
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(title: animacaoRespostaApi());
});
futureData.whenComplete(() {
Navigator.of(context).pop();
print(futureData);
return Navigator.pushNamed(context, args.rota, arguments: ParametrosRetornoConsulta(myController.text, args.url));
});
}
}
But when print it I get an instance of Future instead of the object, and I can't access it's content with futureData['id'] for example.
How can I make futureData stop being an Future and become a iterable object ?
Future<Data> fetchData is Future, you need to use await for data.
onPressed: () async {
if (_formKey.currentState!.validate()) {
var futureData = await fetchData(myController.text, args.url);