How to print the notification content in the terminal - Flutter - flutter

I am trying to print the content of the notification that comes through PHP (topics) How to print the content of the notification in the terminal using Flutter?
code:
class PushNotificationService {
late BuildContext context;
PushNotificationService({required this.context});
Future initialise() async {
FirebaseMessaging.instance.subscribeToTopic('TopicToListen');
print('firebase_token->initialize===${messaging == null}==');
iOSPermission();
print('firebase_token->initialize=///');
messaging.getToken();
print('firebase_token->initialize==**');
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('ic_launcher');
/* const IOSInitializationSettings initializationSettingsIOS = IOSInitializationSettings();
const MacOSInitializationSettings initializationSettingsMacOS = MacOSInitializationSettings();*/
final DarwinInitializationSettings initializationSettingsIOS =
DarwinInitializationSettings(
requestAlertPermission: false,
requestBadgePermission: false,
requestSoundPermission: false,
onDidReceiveLocalNotification:
(int id, String? title, String? body, String? payload) async {
/* didReceiveLocalNotificationStream.add(
ReceivedNotification(
id: id,
title: title,
body: body,
payload: payload,
),
);*/
},
);
/*const InitializationSettings initializationSettings =
InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIOS,
macOS: initializationSettingsMacOS);*/
final InitializationSettings initializationSettings =
InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIOS,
);
/*flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: (String? payload) async {
print("payload*****$payload");
selectNotificationPayload(payload);
});*/
await flutterLocalNotificationsPlugin.initialize(
initializationSettings,
onDidReceiveNotificationResponse:
(NotificationResponse notificationResponse) {
switch (notificationResponse.notificationResponseType) {
case NotificationResponseType.selectedNotification:
selectNotificationPayload(notificationResponse.payload!);
break;
case NotificationResponseType.selectedNotificationAction:
print(
"notification-action-id--->${notificationResponse.actionId}==${notificationResponse.payload}");
break;
}
},
onDidReceiveBackgroundNotificationResponse: backgroundMessage,
);
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
if (message.data != null) {
var data = message.data;
var notif = message.notification;
if (data['type'] == "default" || data['type'] == "category") {
var title = data['title'].toString();
var body = data['message'].toString();
var image = data['image'];
var payload = data["news_id"];
if (payload == null) {
payload = "";
} else {
payload = payload;
}
if (image != null && image != "") {
if (notiEnable!) {
generateImageNotication(title, body, image, payload);
}
} else {
if (notiEnable!) {
generateSimpleNotication(title, body, payload);
}
}
} else {
//Direct Firebase Notification
var title = notif?.title.toString();
var msg = notif?.body.toString();
var img = notif?.android?.imageUrl.toString();
if (notiEnable!) {
(img != null)
? generateImageNotication(title!, msg!, img, '')
: generateSimpleNotication(title!, msg!, '');
}
}
}
});
messaging.getInitialMessage().then((RemoteMessage? message) async {
bool back = await getPrefrenceBool(ISFROMBACK);
print("message******$message");
if (message != null && back) {
var data = message.data;
var notif = message.notification;
if (data['type'] == "default" || data['type'] == "category") {
var title = data['title'].toString();
var body = data['message'].toString();
var image = data['image'];
var payload = data["news_id"];
if (payload == null) {
payload = "";
} else {
payload = payload;
}
if (image != null && image != "") {
if (notiEnable!) {
generateImageNotication(title, body, image, payload);
}
} else {
if (notiEnable!) {
generateSimpleNotication(title, body, payload);
}
}
} else {
//Direct Firebase Notification
var title = notif?.title.toString();
var msg = notif?.body.toString();
var img = notif?.android?.imageUrl.toString();
if (notiEnable!) {
(img != null)
? generateImageNotication(title!, msg!, img, '')
: generateSimpleNotication(title!, msg!, '');
}
}
setPrefrenceBool(ISFROMBACK, false);
}
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async {
print("message******$message");
if (message.data != null) {
var data = message.data;
var notif = message.notification;
if (data['type'] == "default" || data['type'] == "category") {
var title = data['title'].toString();
var body = data['message'].toString();
var image = data['image'];
var payload = data["news_id"];
if (payload == null) {
payload = "";
} else {
payload = payload;
}
if (image != null && image != "") {
if (notiEnable!) {
generateImageNotication(title, body, image, payload);
}
} else {
if (notiEnable!) {
generateSimpleNotication(title, body, payload);
}
}
} else {
//Direct Firebase Notification
var title = notif?.title.toString();
var msg = notif?.body.toString();
var img = notif?.android?.imageUrl.toString();
if (notiEnable!) {
(img != null)
? generateImageNotication(title!, msg!, img, '')
: generateSimpleNotication(title!, msg!, '');
}
}
}
setPrefrenceBool(ISFROMBACK, false);
});
}
void iOSPermission() async {
await messaging.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
}
//when open dynamic link news index and id can used for fetch specific news
Future<void> getNewsById(
String id,
String index,
) async {
var param = {
NEWS_ID: id,
ACCESS_KEY: access_key,
// ignore: unnecessary_null_comparison
USER_ID: CUR_USERID != null && CUR_USERID != "" ? CUR_USERID : "0",
LANGUAGE_ID: CUR_LANGUAGE_ID
};
var apiName = getNewsByIdApi;
http.Response response = await http
.post(Uri.parse(apiName), body: param, headers: headers)
.timeout(Duration(seconds: timeOut));
var getdata = json.decode(response.body);
String error = getdata["error"];
if (error == "false") {
var data = getdata["data"];
List<News> news = [];
news = (data as List).map((data) => new News.fromJson(data)).toList();
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => NewsDetails(
model: news[0],
index: int.parse(index),
id: news[0].id,
isDetails: true,
news: [],
)));
}
}
selectNotificationPayload(String? payload) async {
if (payload != null && payload != "") {
debugPrint('notification payload: $payload');
getNewsById(payload, "0");
} else {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => MyApp()),
);
}
}
}
I searched so much in the firebase documentation and couldn't find anything that could help me with the issue.
I would appreciate your help

I inserted the following line into the main.dart file
And I got the notification content in the terminal.
Hope this helps people in the future
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
print('Handling a background message ${message.notification?.body}');
}

Related

I am not getting image in push notifications when I triggered it from firebase

The below is the code for notification. Can anyone say what is the mistake or is there any code to add to get the image displayed in notification tray. I am using local notifications plugin to get the notifications. The below is the code for notification. Can anyone say what is the mistake or is there any code to add to get the image displayed in notification tray. I am using local notifications plugin to get the notifications.
class LocalNotificationService {
static var userId = SharedUtils.getString('UserId');
FirebaseMessaging messaging = FirebaseMessaging.instance;
static final FlutterLocalNotificationsPlugin _notificationsPlugin =
FlutterLocalNotificationsPlugin();
static getText() {
String title;
String body;
}
static void initialize(BuildContext context) {
final InitializationSettings initializationSettings =
InitializationSettings(
android: AndroidInitializationSettings("#mipmap/ic_launcher"),
iOS: IOSInitializationSettings(
requestSoundPermission: false,
requestBadgePermission: false,
requestAlertPermission: false));
_notificationsPlugin.initialize(initializationSettings,
onSelectNotification: (String route) async {
String userId = SharedUtils.getString('UserId');
if (userId.isEmpty && userId == "") {
SharedUtils.setString('route', route);
Navigator.push(
context, MaterialPageRoute(builder: (context) => LoginView()));
} else {
Navigator.of(Utils.navigatorKey.currentContext)
.push(Routes.generateRoute(route));
}
});
}
static Future initializeFCM(String userId) async {
String uId = userId + "userSpecific";
FirebaseMessaging.instance.subscribeToTopic("public");
FirebaseMessaging.instance.subscribeToTopic(uId);
print(">>>>>>> fcm started $uId");
FirebaseMessaging.instance.getToken().then((value) {
print("fcm token >>> " + value);
});
}
static void display(RemoteMessage message) async {
AndroidNotificationChannel channel = AndroidNotificationChannel(
'high_importance_channel', // id
'High Importance Notifications', // title
//'This channel is used for important notifications.', // description
importance: Importance.high,
playSound: true);
try {
final id = DateTime.now().millisecondsSinceEpoch ~/ 1000;
final NotificationDetails notificationDetails = NotificationDetails(
android: AndroidNotificationDetails(
channel.id,
channel.name,
//channel.description,
color: Colors.blue,
playSound: true,
icon: '#mipmap/ic_launcher',
));
await _notificationsPlugin.show(
id,
message.notification.title,
message.notification.body,
notificationDetails,
payload: message.data["route"],
);
} on Exception catch (e) {
debugPrint(e.toString());
}
}
static onForeground() {
Map notificationData;
FirebaseMessaging.onMessage.listen((message) {
// LocalNotificationService.initializeFCM(userId);
if (message.notification != null) {
notificationData = {
"title": message.notification.title,
"body": message.notification.body
};
}
LocalNotificationService.display(message);
});
return notificationData;
}
static onTerminated() {
FirebaseMessaging.instance.getInitialMessage().then((message) {
if (message != null) {
final routeFromMessage = message.data["route"];
debugPrint('Route called for terminated state***************');
String userId = SharedUtils.getString('UserId');
if (userId.isEmpty && userId == "") {
SharedUtils.setString('route', routeFromMessage);
Navigator.push(Utils.navigatorKey.currentContext,
MaterialPageRoute(builder: (context) => LoginView()));
} else {
Navigator.of(Utils.navigatorKey.currentContext)
.push(Routes.generateRoute(routeFromMessage));
}
}
});
}
static onMessageOpened() {
FirebaseMessaging.onMessageOpenedApp.listen((message) {
final routeFromMessage = message.data["route"];
print('Route called for background***************');
String userId = SharedUtils.getString('UserId');
Map notificationData = {
"title": message.notification.title,
"body": message.notification.body
};
if (userId.isEmpty && userId == "") {
SharedUtils.setString('route', routeFromMessage);
Navigator.push(Utils.navigatorKey.currentContext,
MaterialPageRoute(builder: (context) => LoginView()));
} else {
Navigator.of(Utils.navigatorKey.currentContext, rootNavigator: true)
.push(Routes.generateRoute(routeFromMessage));
}
return notificationData;
});
}
}

I am having problem with onSelect notifcation webengage via FCM notification while onBackground state(onPaused) for my Flutter Application

TL;DR
I am receiving notification on all state(foreground, background(on-Paused), terminated(on-Detached)) but it's redirecting me to the intended url only in(foreground and terminated state). Surprisingly, on receiving notification during foreground state, on-select notification works on-Background state(on-Paused) as well and I am redirected to my intended url. But the main problem is while recieving notification on-background state(on-Paused) without receiving notification on foreground at first, it just redirects me to where I was. Here is the code I am currently working on:
void afterInitFirebase() async {
NotificationSettings settings = await _firebaseMessaging.requestPermission(
alert: true,
announcement: false,
badge: true,
carPlay: false,
criticalAlert: false,
provisional: false,
sound: true,
);
await _firebaseMessaging
.subscribeToTopic(topic)
.onError((error, stackTrace) => {print(error)});
await _firebaseMessaging.getToken().then((value) => {
Preference.setString(fcm_token, value),
});
await _firebaseMessaging.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
await _firebaseMessaging.getInitialMessage().then((RemoteMessage? message) {
if (message != null) {
// _handleIncomingLinks();
MyNotification().initMessaging(message, isPush: true);
}
});
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
displayNotification(message);
});
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
print("${message.from} =--> ON MESSAGE OPENED APP");
});
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
HttpOverrides.global = MyHttpOverrides();
try {
await Firebase.initializeApp().then((value) {
afterInitFirebase();
});
} catch (e) {
print(
"EXCEPTION ON MAIN:" + e.toString(),
);
}
final NotificationAppLaunchDetails? notificationAppLaunchDetails =
await fitNotification.getNotificationAppLaunchDetails();
final uri = await getInitialUri();
String initialRoute = splash_page;
String id = "0";
if (notificationAppLaunchDetails!.didNotificationLaunchApp) {
selectedNotificationPayload = notificationAppLaunchDetails.payload;
print("payload $selectedNotificationPayload");
var parts = selectedNotificationPayload?.split(SEPARATOR);
if (parts != null) {
if (parts[0] == "course" ||
parts[0].toLowerCase() == "coursedetails" ||
parts[0].toLowerCase() == "coursedetails") {
id = parts[1];
//course details page
initialRoute = course_details;
} else if (parts[0].toLowerCase() == "allcourse") {
initialRoute = all_course;
if (parts.length > 1) {
id = parts[1];
}
print("payload: $initialRoute $id");
} else if (parts[0].toLowerCase() == "allplan") {
if (parts.length > 1) {
id = parts[1];
}
initialRoute = "/allPlans";
} else if (parts[0].toLowerCase() == "web") {
id = parts[1];
initialRoute = web_page;
} else if (parts[0].toLowerCase() == "plan") {
id = parts[1];
initialRoute = plans_details_page;
} else if (parts[0].toLowerCase() == "quiz") {
initialRoute = web_page_entrance;
} else if (parts[0].toLowerCase() == "wishlist") {
initialRoute = route_wish_list;
} else if (parts[0].toLowerCase() == "carts") {
initialRoute = my_carts;
} else {
initialRoute = notification_page;
}
}
}
if (uri == null) {
} else {
String path = uri.toString();
if (path.toLowerCase().contains("coursedetails") ||
path.toLowerCase().contains("/home/course")) {
String idStr = uri.path.substring(uri.path.lastIndexOf('/') + 1);
id = idStr;
initialRoute = course_details;
}
}
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
displayNotification(message);
}
void displayNotification(RemoteMessage message) {
log(message.data.toString());
MyNotification().initMessaging(message);
}
This is my MyNotification class:
const String SEPARATOR = "|";
class MyNotification {
void initMessaging(RemoteMessage message, {isPush: true}) async {
var androidInit = AndroidInitializationSettings('ic_notification');
var iosInit = IOSInitializationSettings();
var initSetting =
InitializationSettings(android: androidInit, iOS: iosInit);
await fitNotification.initialize(initSetting,
onSelectNotification: onSelectNotification);
var rand = new Random();
int id = 1;
String? title = "";
String? body = "";
String? icon = "";
String? type = "";
String? itemId = "";
String link = "";
if (message.notification != null) {
title = "${message.notification?.title}";
body = "${message.notification?.body}";
icon = "${message.notification?.android?.imageUrl}";
if (Platform.isAndroid) {
icon = "${message.notification?.android?.imageUrl}";
} else {
icon = "${message.notification?.apple?.imageUrl}";
}
}
if (message.data['source'] == "webengage") {
isPush = true;
Map<String, dynamic> messageData =
jsonDecode(message.data['message_data']);
if (messageData.containsKey("title")) {
title = messageData["title"];
body = messageData["message"];
}
if (messageData.containsKey("expandableDetails")) {
Map<String, dynamic> expDetail = messageData["expandableDetails"];
if (expDetail.containsKey("image")) {
icon = expDetail["image"];
}
if (expDetail.containsKey("style")) {
if (expDetail['style'] == "RATING_V1" ||
expDetail['style'] == "CAROUSEL_V1") {
isPush = false;
}
}
}
if (messageData.containsKey("custom")) {
List<dynamic> customData = messageData['custom'];
print("element1: ${customData.toString()}");
customData.forEach((element) {
Map<String, dynamic> maps = element;
var key = maps['key'];
var value = maps['value'];
if (key == "itemId") {
itemId = value;
}
if (key == "type") {
type = value;
}
});
}
} else {
if (message.data.containsKey("icon")) {
icon = message.data['icon'];
}
if (message.data.containsKey("title")) {
title = message.data['title'];
body = message.data['body'];
}
if (message.data.containsKey("type")) {
type = message.data['type'];
}
if (message.data.containsKey("itemId")) {
itemId = message.data["itemId"];
}
}
if (title?.isNotEmpty == true && body?.isNotEmpty == true) {
showNotification(rand.nextInt(1000), title, body, icon,
"${type}$SEPARATOR${itemId}$SEPARATOR${icon}",
isPush: isPush);
}
}
Future<Uint8List> _getByteArrayFromUrl(String url) async {
final http.Response response = await http.get(Uri.parse(url));
return response.bodyBytes;
}
Future<void> showNotification(int notificationId, String? notificationTitle,
String? notificationContent, String? icon, String payload,
{String channelId = '1234',
String channelTitle = 'Android Channel',
String channelDescription = 'Default Android Channel for notifications',
Priority notificationPriority = Priority.high,
Importance notificationImportance = Importance.max,
bool isPush = true}) async {
//with icon
if (icon != null && icon.isNotEmpty) {
final String bigPicturePath =
await _downloadAndSaveFile(icon, 'bigPicture.jpg');
final BigPictureStyleInformation bigPictureStyleInformation =
BigPictureStyleInformation(
FilePathAndroidBitmap(bigPicturePath),
largeIcon: FilePathAndroidBitmap(bigPicturePath),
);
var androidPlatformChannelSpecifics = new AndroidNotificationDetails(
channelId, channelTitle,
channelDescription: channelDescription,
playSound: false,
importance: notificationImportance,
priority: notificationPriority,
styleInformation: bigPictureStyleInformation,
icon: 'for_icon',
);
final IOSNotificationDetails iOSPlatformChannelSpecifics =
IOSNotificationDetails(attachments: <IOSNotificationAttachment>[
IOSNotificationAttachment(bigPicturePath)
]);
final MacOSNotificationDetails macOSPlatformChannelSpecifics =
MacOSNotificationDetails(attachments: <MacOSNotificationAttachment>[
MacOSNotificationAttachment(bigPicturePath)
]);
final NotificationDetails notificationDetails = NotificationDetails(
iOS: iOSPlatformChannelSpecifics,
macOS: macOSPlatformChannelSpecifics,
android: androidPlatformChannelSpecifics);
if (isPush) {
await fitNotification.show(
notificationId,
notificationTitle,
notificationContent,
notificationDetails,
payload: payload,
);
}
} else {
//with out icon
var androidPlatformChannelSpecifics = new AndroidNotificationDetails(
channelId,
channelTitle,
channelDescription: channelDescription,
playSound: false,
importance: notificationImportance,
priority: notificationPriority,
icon: 'for_icon',
);
final NotificationDetails platformChannelSpecifics =
NotificationDetails(android: androidPlatformChannelSpecifics);
if (isPush) {
await fitNotification.show(notificationId, notificationTitle,
notificationContent, platformChannelSpecifics,
payload: payload);
}
}
var parts = payload.split(SEPARATOR);
var now = new DateTime.now();
var formatter = new DateFormat('yyyy-MM-dd HH:mm:ss');
String formattedDate = formatter.format(now);
NotificationModelData model = NotificationModelData(
courseId: parts[1],
icon: "$icon",
title: notificationTitle.toString(),
description: notificationContent.toString(),
type: parts[0],
notifyTime: formattedDate,
isRead: false,
id: now.millisecondsSinceEpoch.toString());
var db = AppDatabase.instance;
db.into(db.notificationModel).insert(model).then(
(value) => print(value),
);
}
Future<String> _downloadAndSaveFile(String url, String fileName) async {
final Directory directory = await getApplicationDocumentsDirectory();
final String filePath = '${directory.path}/$fileName';
final http.Response response = await http.get(Uri.parse(url));
final File file = File(filePath);
await file.writeAsBytes(response.bodyBytes);
return filePath;
}
Future<dynamic> onSelectNotification(String? payload) async {
selectedNotificationPayload = payload;
var parts = payload!.split(SEPARATOR);
if (parts[0].toLowerCase() == "course") {
//course details page
Navigator.pushNamed(
navigatorKey.currentState!.overlay!.context, course_details,
arguments: <String, String>{
'course_id': parts[1],
'thumbnail': parts[2]
});
} else if (parts[0].toLowerCase() == "web") {
await Navigator.pushNamed(
navigatorKey.currentState!.overlay!.context, web_page,
arguments: <String, String>{'paymentUrl': "${parts[1]}"});
} else if (parts[0].toLowerCase() == "allcourse") {
Navigator.pushNamed(
navigatorKey.currentState!.overlay!.context, all_course,
arguments: <String, String>{'course_id': "${parts[1]}"});
} else if (parts[0].toLowerCase() == "plan") {
Navigator.pushNamed(
navigatorKey.currentState!.overlay!.context, plans_details_page,
arguments: <String, String>{'plan_id': "${parts[1]}"});
} else if (parts[0].toLowerCase() == "allplan") {
Navigator.pushNamed(
navigatorKey.currentState!.overlay!.context, all_plans,
arguments: <String, String>{'id': "${parts[1]}"});
} else if (parts[0].toLowerCase() == "quiz") {
Navigator.of(navigatorKey.currentState!.overlay!.context)
.pushNamed(web_page_entrance);
} else {
//notification page
await Navigator.pushNamed(
navigatorKey.currentState!.overlay!.context, notification_page);
}
}
This is my WebPage class:
var url = "";
class WebPage extends StatefulWidget {
void launchURL() async {
if (await canLaunch(url))
await launch(url);
else
throw "Could not launch $url";
}
#override
_WebPageState createState() => _WebPageState();
}
class _WebPageState extends State<WebPage>{
bool isLoading = true;
final _key = UniqueKey();
Map? _arguments;
var _webViewController;
#override
void initState() {
super.initState();
WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
return widget.launchURL();
});
}
#override
Widget build(BuildContext context) {
_arguments = ModalRoute.of(context)!.settings.arguments as Map?;
if (_arguments?.containsKey("paymentUrl") == true) {
url = _arguments!["paymentUrl"];
} else if (_arguments?.containsKey("course_id") == true) {
url = _arguments!["course_id"];
} else {
print(url);
}
return Scaffold(
body: SplashPage(),
);
}
}
I hope I am being clear enough. I have been stuck for days now on this particular issue with it haunting me in my dreams too. Any help would be greatly appreciated.

" _CastError (Null check operator used on a null value) " Flutter

When interacting with my function that initiate specs, flutter return me this error : "_CastError (Null check operator used on a null value)"
error code :
initSpecsTechs() async {
specs = await APIBike()
.getSpecsTechs(jwt: _user!.jwt, bikeId: favoriteBike!.id);
initBluetoothConnection();
initSliderAutomaticExtinctionValue();
initSliderAutomaticLockingValue();
initBackLightMode();
}
Full code of my page setting (first line contain the error code):
import 'package:myapp/api_bike.dart';
import 'package:myapp/api_user.dart';
import 'package:myapp/ux_components.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'blur_filter.dart';
class SettingsPage extends StatefulWidget {
#override
SettingsPageState createState() => SettingsPageState();
SettingsPage({required Key key}) : super(key: key);
}
class SettingsPageState extends State<SettingsPage>
with AutomaticKeepAliveClientMixin<SettingsPage> {
User? _user;
List<Bike> _bikes = [];
Bike? favoriteBike;
Specs? specs;
bool connectedByBluetooth = true;
double _currentSliderAutomaticExtinctionValue = 0;
double _currentSliderAutomaticLockingValue = 0;
bool theftAlertIsActive = true;
bool batteryAlertIsActive = true;
bool maintainAlertIsActive = true;
bool constantBackLight = true;
double spaceAfterSmallTitle = 15;
double spaceAfterWidget = 20;
//INIT FUNCTIONS
init() async {
await initUser();
await initBikes();
await initFavoriteBike();
await initSpecsTechs();
}
Future initUser() async {
final storage = new FlutterSecureStorage();
String mail = await storage.read(key: "mail") ?? "";
String password = await storage.read(key: "password") ?? "";
UserResponse userResponse = await APIUser()
.login(mail, password); //L'identifiant peut ĂȘtre le username ou le mail
User? user = userResponse.user;
if (user != null) {
setState(() {
_user = user;
});
}
return user;
}
initBikes() async {
if (_user != null) {
List<Bike> bikes = await APIBike().getAllUserBikes(jwt: _user!.jwt);
setState(() {
_bikes = bikes;
});
} else {
print("could not get the bikes call the user is null");
}
}
initFavoriteBike() async {
if (_bikes.length == 0) {
favoriteBike = null;
} else if (_bikes.length == 1) {
setState(() {
favoriteBike = _bikes.first;
});
} else {
Bike? favBike = await APIBike().getFavoriteBike(jwt: _user!.jwt);
if (favBike != null) {
setState(() {
favoriteBike = favBike;
});
} else {
print("PROBLEM : FAVORITE BIKE IS NULL");
}
}
}
initSpecsTechs() async {
specs = await APIBike()
.getSpecsTechs(jwt: _user!.jwt, bikeId: favoriteBike!.id);
initBluetoothConnection();
initSliderAutomaticExtinctionValue();
initSliderAutomaticLockingValue();
initBackLightMode();
}
initBackLightMode() {
if (specs != null) {
bool constantBackLightValue = false;
if (specs!.rearLight == "fixed") {
constantBackLightValue = true;
}
setState(() {
constantBackLight = constantBackLightValue;
});
} else {
print("Fake value used for initBackLightMode");
}
}
initTheftAlertIsActive() {
if (specs != null) {
setState(() {
theftAlertIsActive = specs!.theftAlarm;
});
} else {
print("Fake value used for initStealAlertIsActive");
}
}
initBatteryAlertIsActive() {
if (specs != null) {
setState(() {
batteryAlertIsActive = specs!.batteryAlarm;
});
} else {
print("Fake value used for initBatteryAlertIsActive");
}
}
initMaintenanceAlertIsActive() {
if (specs != null) {
setState(() {
maintainAlertIsActive = specs!.maintenanceAlarm;
});
} else {
print("Fake value used for initMaintenanceAlertIsActive");
}
}
initBluetoothConnection() {
//If this value is false then the page is all grey with nothing active
setState(() {
connectedByBluetooth = true;
});
}
initSliderAutomaticExtinctionValue() {
if (specs != null) {
double sliderValue = 0;
if (specs!.automaticSwitchOff == 0) {
sliderValue = 4;
} else if (specs!.automaticSwitchOff == 5) {
sliderValue = 0;
} else if (specs!.automaticSwitchOff == 10) {
sliderValue = 1;
} else if (specs!.automaticSwitchOff == 15) {
sliderValue = 2;
} else if (specs!.automaticSwitchOff == 20) {
sliderValue = 3;
} else {
//If there is a problem (it is not suppose to happen, we set it to never)
sliderValue = 0;
}
setState(() {
_currentSliderAutomaticExtinctionValue = sliderValue;
});
} else {
print("Fake value used for initSliderAutomaticExtinctionValue");
}
}
initSliderAutomaticLockingValue() {
if (specs != null) {
double sliderValue = 0;
if (specs!.automaticLocking == 0) {
sliderValue = 4;
} else if (specs!.automaticLocking == 1) {
sliderValue = 0;
} else if (specs!.automaticLocking == 3) {
sliderValue = 1;
} else if (specs!.automaticLocking == 5) {
sliderValue = 2;
} else if (specs!.automaticLocking == 10) {
sliderValue = 3;
} else {
//If there is a problem (it is not suppose to happen, we set it to never)
sliderValue = 0;
}
setState(() {
_currentSliderAutomaticLockingValue = sliderValue;
});
} else {
print("Fake value used for initSliderAutomaticLockingValue");
}
}
#override
void initState() {
super.initState();
init();
}
//UPDATE FUNCTIONS
updateRearLightValue(String newValue) async {
//TODO change the value on the bike by BLE
bool operationSucceed = await APIBike().updateSpecsTechs(
jwt: _user!.jwt,
bikeId: favoriteBike!.id,
specToUpdate: "rear_light",
newSpecValue: newValue);
if (operationSucceed) {
print("Rear light value updated successfully");
} else {
print("Error: rear light value didn't update correctly");
}
return operationSucceed;
}
updateAutomaticShutDownValue(double sliderValue) async {
//TODO change the value on the bike by BLE
int newValue = 0; // is also the value for never
if (sliderValue == 0) {
newValue = 5;
} else if (sliderValue == 1) {
newValue = 10;
} else if (sliderValue == 2) {
newValue = 15;
} else if (sliderValue == 3) {
newValue = 20;
} //else the new value is 0 which is never
bool operationSucceed = await APIBike().updateSpecsTechs(
jwt: _user!.jwt,
bikeId: favoriteBike!.id,
specToUpdate: "automatic_switch_off",
newSpecValue: newValue);
if (operationSucceed) {
print("Automatic switch off value updated successfully");
} else {
print("Error: Automatic switch off value didn't update correctly");
}
return operationSucceed;
}
updateAutomaticLockingValue(double sliderValue) async {
//TODO change the value on the bike by BLE
int newValue = 0; // is also the value for never
if (sliderValue == 0) {
newValue = 1;
} else if (sliderValue == 1) {
newValue = 3;
} else if (sliderValue == 2) {
newValue = 5;
} else if (sliderValue == 3) {
newValue = 10;
} //else the new value is 0 which is never
bool operationSucceed = await APIBike().updateSpecsTechs(
jwt: _user!.jwt,
bikeId: favoriteBike!.id,
specToUpdate: "automatic_locking",
newSpecValue: newValue);
if (operationSucceed) {
print("Automatic locking value updated successfully");
} else {
print("Error: Automatic locking value didn't update correctly");
}
return operationSucceed;
}
updateTheftAlertIsActive(bool newValue) async {
bool operationSucceed = await APIBike().updateSpecsTechs(
jwt: _user!.jwt,
bikeId: favoriteBike!.id,
specToUpdate: "theft_alarm",
newSpecValue: newValue);
if (operationSucceed) {
print("Theft alert value updated successfully");
} else {
print("Error: theft alert value didn't update correctly");
}
return operationSucceed;
}
updateBatteryAlertIsActive(bool newValue) async {
bool operationSucceed = await APIBike().updateSpecsTechs(
jwt: _user!.jwt,
bikeId: favoriteBike!.id,
specToUpdate: "battery_alarm",
newSpecValue: newValue);
if (operationSucceed) {
print("Battery alert value updated successfully");
} else {
print("Error: battery alert value didn't update correctly");
}
return operationSucceed;
}
updateMaintenanceAlertIsActive(bool newValue) async {
bool operationSucceed = await APIBike().updateSpecsTechs(
jwt: _user!.jwt,
bikeId: favoriteBike!.id,
specToUpdate: "maintenance_alarm",
newSpecValue: newValue);
if (operationSucceed) {
print("Maintenance alert value updated successfully");
} else {
print("Error: maintenance alert value didn't update correctly");
}
return operationSucceed;
}
/.../
And finally my api for the user :
import 'dart:collection';
import 'package:myapp/api_bike.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class ResponseWithError {
final bool result;
final String error;
ResponseWithError(this.result, this.error);
}
class APIUser {
//For dev
final String serverAddress =
"https://myadress.com/";
//TODO For production:
//final String serverAddress = "https://prodadress.com";
Future<ResponseWithError> createUser(
String firstname, String lastname, String email, String password) async {
var body =
"""{"username": "$firstname $lastname", "firstname": "$firstname", "lastname": "$lastname", "email": "$email", "password": "$password"}""";
var client = new http.Client();
var response = await client.post(
Uri.parse("$serverAddress/auth/local/register"),
body: body,
headers: {"Content-Type": 'application/json'});
if (response.statusCode == 200) {
print("USER CREATED");
print(response.body);
return ResponseWithError(true, "none");
} else {
print("Error " + response.statusCode.toString());
print("Response Body");
print(response.body);
var responseBody = jsonDecode(response.body);
if (responseBody["message"][0]["messages"][0]["id"] != null) {
String errorMessageId = responseBody["message"][0]["messages"][0]["id"];
if (errorMessageId == "Auth.form.error.email.format") {
return ResponseWithError(false, "email format");
}
}
return ResponseWithError(false, "not known");
}
}
Future<UserResponse> login(String identifier, String password) async {
var body = """{"identifier": "$identifier", "password": "$password"}""";
var client = new http.Client();
var response = await client.post(Uri.parse("$serverAddress/auth/local"),
body: body, headers: {"Content-Type": 'application/json'});
if (response.statusCode == 200) {
//print("USER LOGGED IN");
final parsed = jsonDecode(response.body);
User user = User.fromJson(parsed);
UserResponse userResponse = UserResponse(user, true);
return userResponse;
} else {
//print("Error " + response.statusCode.toString());
//print("Response Body");
//print(response.body);
var responseBody = jsonDecode(response.body);
if (responseBody["message"][0]["messages"][0]["id"] != null) {
String errorMessageId = responseBody["message"][0]["messages"][0]["id"];
if (errorMessageId == "Auth.form.error.confirmed") {
print("User not confirmed");
UserResponse userResponse = UserResponse(null, false);
return userResponse;
} else /*if(errorMessageId == "Auth.form.error.invalid")*/ {
print("email or password incorrect");
UserResponse userResponse = UserResponse(null, true);
return userResponse;
}
}
//Should not happen, but just in case
UserResponse userResponse = UserResponse(null, true);
return userResponse;
}
}
Future<bool> updateToken(String jwt, String notificationId) async {
var body = """{"notification_id": "$notificationId"}""";
var client = new http.Client();
var response = await client.put(Uri.parse("$serverAddress/users/token"),
body: body,
headers: {
"Content-Type": 'application/json',
'Authorization': 'Bearer $jwt'
});
if (response.statusCode == 200) {
print("NOTIFICATION TOKEN UPDATED");
return true;
} else {
print("Error " + response.statusCode.toString());
print("Response Body");
print(response.body);
return false;
}
}
Future<bool> updateEmail(String jwt, String newEmail) async {
var confirmed = false;
var body = """{"email": "$newEmail", "confirmed": "$confirmed"}""";
var client = new http.Client();
var response = await client.put(Uri.parse("$serverAddress/users/me"),
body: body,
headers: {
"Content-Type": 'application/json',
'Authorization': 'Bearer $jwt'
});
if (response.statusCode == 200) {
print("EMAIL UPDATED");
return true;
} else {
print("Error " + response.statusCode.toString());
print("Response Body");
print(response.body);
return false;
}
}
Future<bool> updateNames(
String jwt, String newFirstname, String newLastName) async {
var body =
"""{"username": "$newFirstname $newLastName", "firstname": "$newFirstname", "lastname": "$newLastName"}""";
var client = new http.Client();
var response = await client.put(Uri.parse("$serverAddress/users/me"),
body: body,
headers: {
"Content-Type": 'application/json',
'Authorization': 'Bearer $jwt'
});
if (response.statusCode == 200) {
print("NAMES UPDATED");
return true;
} else {
print("Error " + response.statusCode.toString());
print("Response Body");
print(response.body);
return false;
}
}
Future<bool> updatePassword(String jwt, String newPassword) async {
var body = """{"password": "$newPassword"}""";
var client = new http.Client();
var response = await client.put(Uri.parse("$serverAddress/users/me"),
body: body,
headers: {
"Content-Type": 'application/json',
'Authorization': 'Bearer $jwt'
});
if (response.statusCode == 200) {
print("PASSWORD UPDATED");
return true;
} else {
print("Error " + response.statusCode.toString());
print("Response Body");
print(response.body);
return false;
}
}
Future<bool> sendConfirmationCode(String email) async {
var body = """{"email": "$email"}""";
var client = new http.Client();
var response = await client.post(
Uri.parse("$serverAddress/auth/send-email-confirmation"),
body: body,
headers: {"Content-Type": 'application/json'});
if (response.statusCode == 200) {
print("EMAIL SENT to $email");
return true;
} else {
print("Error " + response.statusCode.toString());
print("Response Body");
print(response.body);
return false;
}
}
Future<bool> verifyEmail(String email, String code) async {
var body = """{"email": "$email", "confirmation": "$code"}""";
var client = new http.Client();
var response = await client.put(
Uri.parse("$serverAddress/auth/email-confirmation"),
body: body,
headers: {"Content-Type": 'application/json'});
if (response.statusCode == 200) {
print("EMAIL VERIFIED");
return true;
} else {
print("Error " + response.statusCode.toString());
print("Response Body");
print(response.body);
return false;
}
}
Future<bool> passwordForgotten({required String email}) async {
var body = """{"email": "$email"}""";
var client = new http.Client();
var response = await client.post(
Uri.parse("$serverAddress/auth/forgot-password"),
body: body,
headers: {"Content-Type": 'application/json'});
if (response.statusCode == 200) {
return true;
} else {
print("Error passwordForgotten :" + response.statusCode.toString());
print("Response Body");
print(response.body);
return false;
}
}
}
class UserResponse {
User? user;
bool userConfirmed;
UserResponse(this.user, this.userConfirmed);
}
class User {
final int id;
final String jwt;
final String username;
final String firstname;
final String lastname;
final String email;
final String provider;
final bool confirmed;
final bool? blocked;
final String? notificationId;
final String createdAt;
final String updatedAt;
final List<int>?
bikesId; //Here we just save the ids of the bikes linked to the user
final Subscription? subscription;
final Role role;
User({
required this.id,
required this.jwt,
required this.username,
required this.firstname,
required this.lastname,
required this.email,
required this.provider,
required this.confirmed,
required this.blocked,
required this.notificationId,
required this.createdAt,
required this.updatedAt,
required this.bikesId,
required this.subscription,
required this.role,
});
factory User.fromJson(Map<String, dynamic> json) {
if (json['user']['subscription'] != null) {
return User(
id: json['user']['id'] as int,
jwt: json['jwt'] as String,
username: json['user']['username'] as String,
firstname: json['user']['firstname'] as String,
lastname: json['user']['lastname'] as String,
email: json['user']['email'] as String,
provider: json['user']['provider'] as String,
confirmed: json['user']['confirmed'] as bool,
blocked: json['user']['blocked'] as bool?,
notificationId: json['user']['notification_id'] as String?,
createdAt: json['user']['created_at'] as String,
updatedAt: json['user']['updated_at'] as String,
bikesId: createBikesIdList(json['user']['bikes_id']),
subscription: Subscription(
id: json['user']['subscription']['id'],
name: json['user']['subscription']['name'],
createdAt: json['user']['subscription']['created_at'],
updatedAt: json['user']['subscription']['updated_at']),
role: Role(
id: json['user']['role']['id'],
name: json['user']['role']['name'],
description: json['user']['role']['description'],
type: json['user']['role']['type'],
));
} else {
return User(
id: json['user']['id'] as int,
jwt: json['jwt'] as String,
username: json['user']['username'] as String,
firstname: json['user']['firstname'] as String,
lastname: json['user']['lastname'] as String,
email: json['user']['email'] as String,
provider: json['user']['provider'] as String,
confirmed: json['user']['confirmed'] as bool,
blocked: json['user']['blocked'] as bool?,
notificationId: json['user']['notification_id'] as String?,
createdAt: json['user']['created_at'] as String,
updatedAt: json['user']['updated_at'] as String,
bikesId: createBikesIdList(json['user']['bikes_id']),
subscription: null,
role: Role(
id: json['user']['role']['id'],
name: json['user']['role']['name'],
description: json['user']['role']['description'],
type: json['user']['role']['type'],
));
}
}
describe() {
print("id : $id\njwt : $jwt");
}
}
class Role {
final int id;
final String name;
final String description;
final String type;
Role({
required this.id,
required this.name,
required this.description,
required this.type,
});
}
class Subscription {
final int id;
final String name;
final String createdAt;
final String updatedAt;
Subscription(
{required this.id,
required this.name,
required this.createdAt,
required this.updatedAt});
}
Can you try like this? I think user or favoriteBike is null. You need to null check.
initSpecsTechs() async {
if(_user != null && favoriteBike != null){
specs = await APIBike()
.getSpecsTechs(jwt: _user!.jwt, bikeId: favoriteBike!.id);
initBluetoothConnection();
initSliderAutomaticExtinctionValue();
initSliderAutomaticLockingValue();
initBackLightMode();
}
}
initSpecsTechs() async {
specs = await APIBike()
.getSpecsTechs(jwt: _user!.jwt, bikeId: favoriteBike!.id);
initBluetoothConnection();
initSliderAutomaticExtinctionValue();
initSliderAutomaticLockingValue();
initBackLightMode();
}
The "!" operator causes the exception instead use "?" like this
specs = await APIBike()
.getSpecsTechs(jwt: _user?.jwt??'', bikeId: favoriteBike?.id??'');

"Unable to RTCPeerConnection::setRemoteDescription: Failed to set remote answer sdp: Called in wrong state: kStable"

I am using flutter_webrtc to connect to users peer to peer. I am getting this error on the offer side. I have been looking all over the internet for a fix but couldn't find the solution.
class Webrtc {
bool _offer = false;
RTCPeerConnection _peerConnection;
MediaStream _localStream;
RTCVideoRenderer _localRenderer = new RTCVideoRenderer();
RTCVideoRenderer _remoteRenderer = new RTCVideoRenderer();
get localRe
nderer => _localRenderer;
get remoteRenderer => _remoteRenderer;
//final sdpController = TextEditingController();
Webrtc() {
initRenderers();
_createPeerConnection().then((pc) {
_peerConnection = pc;
});
}
initRenderers() async {
await _localRenderer.initialize();
await _remoteRenderer.initialize();
}
createOffer() async {
_offer = true;
RTCSessionDescription description =
await _peerConnection.createOffer({'offerToReceiveVideo': 1});
// var session = parse(description.sdp);
// print(json.encode(session));
// _offer = true;
var roomDef = Firestore.instance.collection("rooms").document("test");
var data = {
"offer": {
'sdp': description.sdp.toString(),
'type': description.type.toString(),
}
};
await roomDef.setData(data, merge: true);
await _peerConnection.setLocalDescription(description);
Firestore.instance.collection("rooms").document("test").snapshots().listen((event) {
if(event.data["answer"] != null){
_setRemoteDescription(event.data["answer"]);
}
});
}
createAnswer() async {
// Firestore.instance.collection("rooms").document("test").snapshots().listen((event) {
// if(event.data["offer"] != null){
// _setRemoteDescription(event.data["offer"]);
// }
// });
var doc = await Firestore.instance.collection("rooms").document("test").get();
print(doc.data["offer"]);
await _setRemoteDescription(doc.data["offer"]);
RTCSessionDescription description =
await _peerConnection.createAnswer({'offerToReceiveVideo': 1});
//var session = parse(description.sdp);
//print(json.encode(session));
await _peerConnection.setLocalDescription(description);
var data = {
"answer": {
'sdp': description.sdp.toString(),
'type': description.type.toString(),
}
};
Firestore.instance
.collection("rooms")
.document("test")
.setData(data, merge: true);
}
_setRemoteDescription(doc) async {
// String jsonString = doc.toString();
// dynamic session = await jsonDecode('$jsonString');
//String sdp = write(session, null);
// RTCSessionDescription description =
// new RTCSessionDescription(session['sdp'], session['type']);
RTCSessionDescription description =
new RTCSessionDescription(doc["sdp"],doc["type"]);
print(description.toMap());
await _peerConnection.setRemoteDescription(description);
}
void _addCandidate(data) async {
dynamic session = data;
dynamic candidate = new RTCIceCandidate(
session['candidate'], session['sdpMid'], session['sdpMlineIndex']);
await _peerConnection.addCandidate(candidate);
}
_createPeerConnection() async {
Map<String, dynamic> configuration = {
"iceServers": [
{"url": "stun:stun.l.google.com:19302"},
]
};
final Map<String, dynamic> offerSdpConstraints = {
"mandatory": {
"OfferToReceiveAudio": true,
"OfferToReceiveVideo": true,
},
"optional": [],
};
_localStream = await _getUserMedia();
RTCPeerConnection pc =
await createPeerConnection(configuration, offerSdpConstraints);
// if (pc != null) print(pc);
pc.addStream(_localStream);
pc.onIceCandidate = (e) {
if (_offer && e.candidate != null) {
Firestore.instance.collection("caller").add({
'candidate': e.candidate.toString(),
'sdpMid': e.sdpMid.toString(),
'sdpMlineIndex': e.sdpMlineIndex,
});
Firestore.instance.collection("callee").snapshots().listen((event) {
event.documentChanges.forEach((element) {
print(element.document.data);
_addCandidate(element.document.data);
});
});
}
if (!_offer && e.candidate != null) {
Firestore.instance.collection("callee").add({
'candidate': e.candidate.toString(),
'sdpMid': e.sdpMid.toString(),
'sdpMlineIndex': e.sdpMlineIndex,
});
Firestore.instance.collection("caller").snapshots().listen((event) {
event.documentChanges.forEach((element) {
print(element.document.data);
_addCandidate(element.document.data);
});
});
}
// if (e.candidate != null) {
// print(json.encode({
// 'candidate': e.candidate.toString(),
// 'sdpMid': e.sdpMid.toString(),
// 'sdpMlineIndex': e.sdpMlineIndex,
// }));
// }
};
pc.onIceConnectionState = (e) {
print(e);
};
pc.onAddStream = (stream) {
print('addStream: ' + stream.id);
_remoteRenderer.srcObject = stream;
};
return pc;
}
_getUserMedia() async {
final Map<String, dynamic> mediaConstraints = {
'audio': true,
'video': {
'facingMode': 'user',
},
};
MediaStream stream = await navigator.getUserMedia(mediaConstraints);
// _localStream = stream;
_localRenderer.srcObject = stream;
_localRenderer.mirror = true;
// _peerConnection.addStream(stream);
return stream;
}
}
I tried switching the setlocaldescption and setremotedescption but no use. Offer button calls create offer from the ui and answer button calls createanswer function.
I cannot comment yet under your post but I had this problem with the newest version of flutter_webrtc. Three days of head-scratching later and I used:
flutter_webrtc: ^0.2.7
Worked on the first try. Cheers.

Flutter WebRTC audio but no video

So I'm building a video calling application using flutter, flutterWeb, and the WebRTC package.
I have a spring boot server sitting in the middle to pass the messages between the two clients.
Each side shows the local video, but neither shows the remote. Audio does work though. I got som nasty feedback loops. Testing with headphones showed that audio does indeed work.
My singaling code
typedef void StreamStateCallback(MediaStream stream);
class CallingService {
String sendToUserId;
String currentUserId;
final String authToken;
final StompClient _client;
final StreamStateCallback onAddRemoteStream;
final StreamStateCallback onRemoveRemoteStream;
final StreamStateCallback onAddLocalStream;
RTCPeerConnection _peerConnection;
List<RTCIceCandidate> _remoteCandidates = [];
String destination;
var hasOffer = false;
var isNegotiating = false;
MediaStream _localStream;
final Map<String, dynamic> _constraints = {
'mandatory': {
'OfferToReceiveAudio': true,
'OfferToReceiveVideo': true,
},
'optional': [],
};
CallingService(
this._client,
this.sendToUserId,
this.currentUserId,
this.authToken,
this.onAddRemoteStream,
this.onRemoveRemoteStream,
this.onAddLocalStream) {
destination = '/app/start-call/$sendToUserId';
print("destination $destination");
_client.subscribe(
destination: destination,
headers: {'Authorization': "$authToken"},
callback: (StompFrame frame) => processMessage(jsonDecode(frame.body)));
}
Future<void> startCall() async {
await processRemoteStream();
RTCSessionDescription description =
await _peerConnection.createOffer(_constraints);
await _peerConnection.setLocalDescription(description);
var message = RtcMessage(RtcMessageType.OFFER, currentUserId, {
'description': {'sdp': description.sdp, 'type': description.type},
});
sendMessage(message);
}
Future<void> processMessage(Map<String, dynamic> messageJson) async {
var message = RtcMessage.fromJson(messageJson);
if (message.from == currentUserId) {
return;
}
print("processing ${message.messageType.toString()}");
switch (message.messageType) {
case RtcMessageType.BYE:
// TODO: Handle this case.
break;
case RtcMessageType.LEAVE:
// TODO: Handle this case.
break;
case RtcMessageType.CANDIDATE:
await processCandidate(message);
break;
case RtcMessageType.ANSWER:
await processAnswer(message);
break;
case RtcMessageType.OFFER:
await processOffer(message);
break;
}
}
Future<void> processCandidate(RtcMessage candidate) async {
Map<String, dynamic> map = candidate.data['candidate'];
var rtcCandidate = RTCIceCandidate(
map['candidate'],
map['sdpMid'],
map['sdpMLineIndex'],
);
if (_peerConnection != null) {
_peerConnection.addCandidate(rtcCandidate);
} else {
_remoteCandidates.add(rtcCandidate);
}
}
Future<void> processAnswer(RtcMessage answer) async {
if (isNegotiating) {
return;
}
isNegotiating = true;
var description = answer.data['description'];
if (_peerConnection == null) {
return;
}
await _peerConnection.setRemoteDescription(
RTCSessionDescription(description['sdp'], description['type']));
}
Future<void> processOffer(RtcMessage offer) async {
await processRemoteStream();
var description = offer.data['description'];
await _peerConnection.setRemoteDescription(
new RTCSessionDescription(description['sdp'], description['type']));
var answerDescription = await _peerConnection.createAnswer(_constraints);
await _peerConnection.setLocalDescription(answerDescription);
var answerMessage = RtcMessage(RtcMessageType.ANSWER, currentUserId, {
'description': {
'sdp': answerDescription.sdp,
'type': answerDescription.type
},
});
sendMessage(answerMessage);
if (_remoteCandidates.isNotEmpty) {
_remoteCandidates
.forEach((candidate) => _peerConnection.addCandidate(candidate));
_remoteCandidates.clear();
}
}
Future<void> processRemoteStream() async {
_localStream = await createStream();
_peerConnection = await createPeerConnection(_iceServers, _config);
_peerConnection.addStream(_localStream);
_peerConnection.onSignalingState = (state) {
//isNegotiating = state != RTCSignalingState.RTCSignalingStateStable;
};
_peerConnection.onAddStream = (MediaStream stream) {
this.onAddRemoteStream(stream);
};
_peerConnection.onRemoveStream =
(MediaStream stream) => this.onRemoveRemoteStream(stream);
_peerConnection.onIceCandidate = (RTCIceCandidate candidate) {
var data = {
'candidate': {
'sdpMLineIndex': candidate.sdpMlineIndex,
'sdpMid': candidate.sdpMid,
'candidate': candidate.candidate,
},
};
var message = RtcMessage(RtcMessageType.CANDIDATE, currentUserId, data);
sendMessage(message);
};
}
void sendMessage(RtcMessage message) {
_client.send(
destination: destination,
headers: {'Authorization': "$authToken"},
body: jsonEncode(message.toJson()));
}
Map<String, dynamic> _iceServers = {
'iceServers': [
{'urls': 'stun:stun.l.google.com:19302'},
/*
* turn server configuration example.
{
'url': 'turn:123.45.67.89:3478',
'username': 'change_to_real_user',
'credential': 'change_to_real_secret'
},
*/
]
};
final Map<String, dynamic> _config = {
'mandatory': {},
'optional': [
{'DtlsSrtpKeyAgreement': true},
],
};
Future<MediaStream> createStream() async {
final Map<String, dynamic> mediaConstraints = {
'audio': true,
'video': {
'mandatory': {
'minWidth': '640',
'minHeight': '480',
'minFrameRate': '30',
},
'facingMode': 'user',
'optional': [],
}
};
MediaStream stream = await navigator.getUserMedia(mediaConstraints);
if (this.onAddLocalStream != null) {
this.onAddLocalStream(stream);
}
return stream;
}
}
Here are my widgets
class _CallScreenState extends State<CallScreen> {
StompClient _client;
CallingService _callingService;
RTCVideoRenderer _localRenderer = new RTCVideoRenderer();
RTCVideoRenderer _remoteRenderer = new RTCVideoRenderer();
final UserService userService = GetIt.instance.get<UserService>();
void onConnectCallback(StompClient client, StompFrame connectFrame) async {
var currentUser = await userService.getCurrentUser();
_callingService = CallingService(
_client,
widget.intent.toUserId.toString(),
currentUser.id.toString(),
widget.intent.authToken,
onAddRemoteStream,
onRemoveRemoteStream,
onAddLocalStream);
if (widget.intent.initialMessage != null) {
_callingService.processMessage(jsonDecode(widget.intent.initialMessage));
} else {
_callingService.startCall();
}
}
void onAddRemoteStream(MediaStream stream) {
_remoteRenderer.srcObject = stream;
}
void onRemoveRemoteStream(MediaStream steam) {
_remoteRenderer.srcObject = null;
}
void onAddLocalStream(MediaStream stream) {
_localRenderer.srcObject = stream;
}
#override
void initState() {
super.initState();
_localRenderer.initialize();
_remoteRenderer.initialize();
_client = StompClient(
config: StompConfig(
url: 'ws://${DomainService.getDomainBase()}/stomp',
onConnect: onConnectCallback,
onWebSocketError: (dynamic error) => print(error.toString()),
stompConnectHeaders: {'Authorization': "${widget.intent.authToken}"},
onDisconnect: (message) => print("disconnected ${message.body}"),),
);
_client.activate();
}
#override
Widget build(BuildContext context) {
return PlatformScaffold(
pageTitle: "",
child: Flex(
direction: Axis.vertical,
children: [
Flexible(
flex: 1,
child: RTCVideoView(_localRenderer),
),
Flexible(
flex: 1,
child: RTCVideoView(_remoteRenderer),
)
],
),
);
}
}
I put a print statment in the the widget on the addRemoteStream callback, and it's getting called. So some kind of stream is being sent. I'm just not sure why the video isnt' showing.
So my problem was that I wasn't adding queued candidates to the caller.
I added
sendMessage(answerMessage);
if (_remoteCandidates.isNotEmpty) {
_remoteCandidates
.forEach((candidate) => _peerConnection.addCandidate(candidate));
_remoteCandidates.clear();
}
to the processAnswer method and it works just fine!