Flutter: Is there a way to get quit event in web application? - flutter

I want to call an API when the user closes or reloads the webpage(web application).
listen event
html.window.onBeforeUnload.listen(
(event) async {
Map<String, dynamic> _exit = await callApi(CloseRoomAPI);
print(_exit);
},
);
Call API function
Future<Map<String, dynamic>> callApi(String _url) async {
final client = new http.Client();
final connection = await client.get(_url);
if (connection.statusCode == 200) {
return jsonDecode(connection.body.toString());
} else {
return null;
}
}

Related

flutter_webrtc: Cannot rejoin to the channel I left earlier

I am developing a streaming application: one application (server) broadcasts a video, and another application (client) displays this video. I used the flutter_webrtc package for real-time communication. I followed the following tutroial:
https://www.youtube.com/watch?v=hAKQzNQmNe0
https://github.com/md-weber/webrtc_tutorial/
Currently, the server application can successfully create a channel and broadcast the video, and the client application can join this channel and watch the video. But when the client leaves the channel and later tries to connect to the same channel, it can't get the video, only a black screen is displayed. There are no errors shown.
I used flutter_riverpod as state management and all codes below are inside StateNotifiers.
Function to create a channel from server application side.
Future<String> startStream(RTCVideoRenderer localVideo) async {
state = ProcessState.loading;
final stream = await navigator.mediaDevices.getUserMedia(<String, dynamic>{
'video': true,
'audio': true,
});
localVideo.srcObject = stream;
localStream = stream;
final roomId = await _createRoom();
await Wakelock.enable();
state = ProcessState.working;
return roomId;
}
Future<String> _createRoom() async {
final db = FirebaseFirestore.instance;
final roomRef = db.collection('rooms').doc();
peerConnection = await createPeerConnection(configuration);
localStream?.getTracks().forEach((track) {
peerConnection?.addTrack(track, localStream!);
});
// Code for collecting ICE candidates below
final callerCandidatesCollection = roomRef.collection('callerCandidates');
peerConnection?.onIceCandidate = (RTCIceCandidate candidate) {
callerCandidatesCollection.add(candidate.toMap() as Map<String, dynamic>);
};
// Add code for creating a room
final offer = await peerConnection!.createOffer();
await peerConnection!.setLocalDescription(offer);
final roomWithOffer = <String, dynamic>{'offer': offer.toMap()};
await roomRef.set(roomWithOffer);
roomId = roomRef.id;
// Listening for remote session description below
roomRef.snapshots().listen((snapshot) async {
final data = snapshot.data();
if (peerConnection?.getRemoteDescription() != null && data != null && data['answer'] != null){
final answer = data['answer'] as Map<String, dynamic>;
final description = RTCSessionDescription(
answer['sdp'] as String?,
answer['type'] as String?,
);
await peerConnection?.setRemoteDescription(description);
}
});
// Listen for remote Ice candidates below
roomRef.collection('calleeCandidates').snapshots().listen((snapshot) {
for (final change in snapshot.docChanges) {
if (change.type == DocumentChangeType.added) {
final data = change.doc.data();
peerConnection!.addCandidate(
RTCIceCandidate(
data?['candidate'] as String?,
data?['sdpMid'] as String?,
data?['sdpMLineIndex'] as int?,
),
);
}
}
});
return roomId!;
}
Function to join a channel from client application side.
Future<bool> startStream(String? roomId, RTCVideoRenderer remoteVideo) async {
if (roomId == null || roomId.isEmpty) {
return false;
}
state = ProcessState.loading;
final result = await _joinRoom(roomId, remoteVideo);
if (result) {
state = ProcessState.working;
await Wakelock.enable();
} else {
state = ProcessState.notInitialized;
}
return result;
}
Future<bool> _joinRoom(String roomId, RTCVideoRenderer remoteVideo) async {
final db = FirebaseFirestore.instance;
final DocumentReference roomRef = db.collection('rooms').doc(roomId);
final roomSnapshot = await roomRef.get();
if (roomSnapshot.exists) {
peerConnection = await createPeerConnection(configuration);
peerConnection?.onAddStream = (MediaStream stream) {
onAddRemoteStream?.call(stream);
remoteStream = stream;
};
// Code for collecting ICE candidates below
final calleeCandidatesCollection = roomRef.collection('calleeCandidates');
peerConnection?.onIceCandidate = (RTCIceCandidate candidate) {
final candidateMap = candidate.toMap() as Map<String, dynamic>;
calleeCandidatesCollection.add(candidateMap);
};
peerConnection?.onTrack = (RTCTrackEvent event) {
event.streams[0].getTracks().forEach((track) {
remoteStream?.addTrack(track);
});
};
// Code for creating SDP answer below
final data = roomSnapshot.data() as Map<String, dynamic>?;
final offer = data?['offer'] as Map<String, dynamic>?;
await peerConnection?.setRemoteDescription(
RTCSessionDescription(
offer?['sdp'] as String?,
offer?['type'] as String?,
),
);
final answer = await peerConnection!.createAnswer();
await peerConnection!.setLocalDescription(answer);
final roomWithAnswer = <String, dynamic>{
'answer': {
'type': answer.type,
'sdp': answer.sdp,
}
};
await roomRef.update(roomWithAnswer);
// Listening for remote ICE candidates below
roomRef.collection('callerCandidates').snapshots().listen((snapshot) {
for (final document in snapshot.docChanges) {
final data = document.doc.data();
peerConnection!.addCandidate(
RTCIceCandidate(
data?['candidate'] as String?,
data?['sdpMid'] as String?,
data?['sdpMLineIndex'] as int?,
),
);
}
});
this.roomId = roomId;
return true;
}
return false;
}
After some research, I found that my problem is the same as in the question Failed to set remote answer sdp: Called in wrong state: stable. This was caused by the fact that one RTCPeerConnection can only establish one peer-to-peer connection. So I could fix this by creating a new RTCPeerConnection on the server side every time a new client wants to join the channel.
Future<String> _createRoom() async {
final db = FirebaseFirestore.instance;
final roomRef = db.collection('rooms').doc();
await newPeerConnection(roomRef);
roomId = roomRef.id;
// Listening for remote session description below
roomRef.snapshots().listen((snapshot) async {
final data = snapshot.data();
if (data != null && data['answer'] != null) {
final answer = data['answer'] as Map<String, dynamic>;
final description = RTCSessionDescription(
answer['sdp'] as String?,
answer['type'] as String?,
);
await peerConnectionList.last.setRemoteDescription(description);
await newPeerConnection(roomRef);
}
});
// Listen for remote Ice candidates below
roomRef.collection('calleeCandidates').snapshots().listen((snapshot) {
for (final change in snapshot.docChanges) {
if (change.type == DocumentChangeType.added) {
final data = change.doc.data();
peerConnectionList.last.addCandidate(
RTCIceCandidate(
data?['candidate'] as String?,
data?['sdpMid'] as String?,
data?['sdpMLineIndex'] as int?,
),
);
}
}
});
return roomId!;
}
Future<void> newPeerConnection(DocumentReference roomRef) async {
final peerConnection = await createPeerConnection(
configuration,
offerSdpConstraints,
);
_registerPeerConnectionListeners(peerConnection);
localStream?.getTracks().forEach((track) {
peerConnection.addTrack(track, localStream!);
});
final offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
final roomWithOffer = <String, dynamic>{'offer': offer.toMap()};
await roomRef.set(roomWithOffer);
peerConnectionList.add(peerConnection);
}

Empty map on flutter when initiating

Map user = {};
Future<void> getUser(String idProfile) async {
final response = await ac.getItem("/v2/users/:0", [idProfile]);
if (response.statusCode >= 200 && response.statusCode < 300) {
setState(() {
user = json.decode(response.body);
print(user);
});
}
}
#override
void initState() {
super.initState();
getUser(getCurrentUser());
print(user);
}
With the first print, it returns me the user. However, at the second doesn't. I need to get the user information. How could I do it?
getUser is a future method, you need to wait until it fetches data from API. While you are using StatefulWidget , you can show landing indication while it fetch data from API.
If it is inside Column widget,
if (user.isEmpty) Text("fetching data")
else LoadDataWidget(),
Also you can use ternary operator.
Map user = {};
//Return a user from the function
Future<Map<String, dynamic>> getUser(String idProfile) async {
final response = await ac.getItem("/v2/users/:0", [idProfile]);
if (response.statusCode >= 200 && response.statusCode < 300) {
user = json.decode(response.body) as Map<String, dynamic>;
return user
}
else {
throw Exception();
}
}
// Set the user value in initstate
#override
void initState() {
super.initState();
user = getUser(getCurrentUser());
print(user);
}

how to set values in initState method using riverpod

right now i have a ChangeNotifierProvider, and i want to set some values straight in the initState method.
those values come from a backend API, that are retrieved in that provider.
I am stuck is this situation for a while now, hope i can get some help.
Here is the ChangeNotifierProvider
final userProvider = ChangeNotifierProvider.autoDispose.family<UserProxy, String>((ref, id) {
var notifier = UserProxy(userId: id);
notifier.load();
return notifier;
});
class UserProxy extends ChangeNotifier {
String userId;
User? user;
UserProxy({this.userId = ""});
void load() async {
await getUser().then((value) => generateObject(value));
}
Future<String> getUser() async {
Map<String, String> queryParams = {
"id": userId,
};
var url = Uri.https("asdadas.asdasd.com", "endpoint", queryParams);
Map<String, String> headers = {
'content-type': "application/json",
};
var response = await http.get(url,
headers: headers,);
print(response.body);
return response.body;
}
User generateObject(String jsonString) {
this.user = User.fromJson(jsonDecode(jsonString));
notifyListeners();
return this.user ?? User();
}
}
For this case I would suggest
FutureProvider.autoDispose.family<UserProxy, String>((ref, id) async { .... })
then change your StateWidget to ConsumerStatefulWidget and ConsumerState<>
then
ref.watch(provider(11)).when(
loading: (){},
error: (Object err, StackTrace? st){ },
data: (user){
// build widget with result here.
},
)

AlarmManager not starting when app is removed from the background

I am trying to get the location data on some particular time. On every time we want to fetch the location data, we are sending a silent notification to our app. When app sees a silent notification It tries to get the data and upload back to the server. Every thing is working fine when app is running in foreground, but when i close the app and clear from the background, alarm manager is not getting fired to fetch and save the location.
_firebaseMessaging.configure(
onMessage: (
Map<String, dynamic> message,
) async {
final Map data =
message.containsKey('data') ? message['data'] as Map : null;
if (data?.isEmpty ?? false) {
_handleNotification(message);
} else {
_handleSilentEvent(message); // This method will be called when server will make call to fetch location data
}
},
onResume: (Map<String, dynamic> message) async {},
onLaunch: (Map<String, dynamic> message) async {},
onBackgroundMessage: _handleSilentEvent,
);
Future<dynamic> _handleSilentEvent(
final Map<String, dynamic> message,
) async {
final Map data = message.containsKey('data') ? message['data'] as Map : null;
if (data?.isNotEmpty ?? false) {
if (data.containsKey('getLocation')) {
await LocationService.triggerServiceToGetAndSaveLocation();
} else {
_handleWebEngage(data);
}
}
}
abstract class LocationService {
static const int _alarmManagerId = 1001;
static final ILocationFacade _locationFacade = getIt<ILocationFacade>();
LocationService._();
static Future<void> triggerServiceToGetAndSaveLocation() async {
AndroidAlarmManager.oneShotAt(
DateTime.now(),
_alarmManagerId,
LocationService._fetchAndSaveLocation,
wakeup: true,
exact: true,
);
}
static Future<void> _fetchAndSaveLocation() async {
final position = await _getCurrentLocation();
final capturedAt = DateTime.now().millisecondsSinceEpoch.toString();
final gpsEnabled = await Geolocator.isLocationServiceEnabled();
final LocationInfo locationInfo = LocationInfo(
lat: position.latitude,
lng: position.longitude,
capturedAt: capturedAt,
gpsEnabled: gpsEnabled,
);
print(
'onMessage:: Lat: ${position.latitude} Long: ${position.longitude} capturedAt: $capturedAt gpsEnabled: $gpsEnabled'); // This print is getting called when app is in foreground, when i kill the app this print is not event getting called.
// await _locationFacade.saveLocationInfo(locationInfo);
}
static Future<Position> _getCurrentLocation() async {
final _gpsServiceEnabled = await Geolocator.isLocationServiceEnabled();
if (_gpsServiceEnabled) {
return Geolocator.getCurrentPosition();
}
return Geolocator.getLastKnownPosition();
}
}
Can someone point me out where i am making the mistake?

Flutter initState wait for async function to complete

in my main.dart i have among others those two functions:
Future<void> _fetchMasterData() async {
print("Start fetch");
var jwt = await API.attemptLogIn();
if (jwt != null) {
Map<String, dynamic> answer = jsonDecode(jwt);
if (answer['message'] == 'Auth ok') {
jwtToken = 'Bearer ' + answer['token'];
}
}
await _getArticles();
await _getMainCategories();
await _getIngredients();
await _getArticleIngredients();
print("EndMasterData fetch");
}
And
#override
void initState() {
super.initState();
_fetchMasterData();
}
What i would like to have is to wait in initState till _fethcMasterData is done bevore Widgert build is called.
Is that possible? Many thanks for any help!
Here how I use an async func in initstate;
builder() async {
favoriteDatabase =
await $FloorFavoriteDatabase.databaseBuilder('favorite_database.db')
.build();
setState(() {
favoriteDao = favoriteDatabase.favoriteDao;
});
}
#override
void initState() {
// TODO: implement initState
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
WidgetsBinding.instance.addPostFrameCallback((_) =>
getNamePreferences().then(updateName));
});
builder();
favoriteDao.findAllMoviesAsStreamW();
favoriteDao.findAllMoviesAsStream();
}
Also you can check this mini article too.
It is not possible to await in initState, so when you finish all loading process then you can call SetState method which populate your widget with actual data.
Second solution could be use of futurebuilder or streambuilder where you want to show data but it is only possible if any methods data is not dependent on each other.
Future<void> _fetchMasterData() async {
print("Start fetch");
var jwt = await API.attemptLogIn();
if (jwt != null) {
Map<String, dynamic> answer = jsonDecode(jwt);
if (answer['message'] == 'Auth ok') {
jwtToken = 'Bearer ' + answer['token'];
}
}
await _getArticles();
await _getMainCategories();
await _getIngredients();
await _getArticleIngredients();
print("EndMasterData fetch");
SetState((){}); // added line
}