Closed walkerJung closed 8 months ago
When I move the flutter app to the background and come back after about 15 minutes, I am logged as 80003 error. Connection temporarily unavailable code= 80003 Do I have to explicitly let you connect at times like this?
Hey @walkerJung,
Thank you for choosing Ably! You don't need to explicitly reconnect after your app comes back from the background, but you need to check the resumed flag on the channel's attached event. When resumed is false, some messages may have been lost while the app was in the background, and you probably need to handle this in your business logic (e.g., refetch data from the backend).
Here is the quote from our FAQ docs about connection recovery:
Once the connection is reestablished, the client library will reattach the suspended channels automatically and emit an attached event with the resumed flag set to false. This ensures that as a developer, you can listen for attached events and check the resumed flag to see if a channel resumed fully and no messages were lost (when resumed is true), or the channel attached but could not resume (when resumed is false).
@ttypic Thank you for your answer, but the status of my realtime provider is not changing from disconnected to connected and all the connections are still disconnected.
On my app, I'm using it by declaring it as a provider to create only one realtime object because they're subscribing to different channels on different screens, is this part wrong?
@walkerJung, it doesn't look like you're doing anything wrong. Can you share some details that can help us understand better:
@ttypic Thank you for your answer,
platform : IOS, Android i use physical device
// ably realtime
final ablyRealtimeProvider = Provider<Realtime>(
(ref) {
final realtime = Realtime(
options: ClientOptions(
key: ABLYAPIKEY,
autoConnect: true,
disconnectedRetryTimeout: 5000,
suspendedRetryTimeout: 5000,
fallbackRetryTimeout: 5000,
channelRetryTimeout: 5000,
logLevel: LogLevel.verbose,
),
);
return realtime;
},
);
The above source code is my realtime object
My app has multiple channels, or chat screens, so I made this to create and write only one realtime object. When I send the app to the background on the screen where the entire user can gather and chat, and after a certain period of time, the realtime.connection.state does not change from disconnected to connected.
It seems to mainly happen on Android devices. After leaving the Android app in the background for more than 30 minutes to an hour, when I move to the foreground, the realtime connection seems to be disconnected and I don't try to reconnect
https://faqs.ably.com/error-code-80003
Fifteen seconds later, it's still disconnected
If you bring the app to the foreground when the above log is no longer taken, the connection does not change in disconnected.
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:ably_flutter/ably_flutter.dart';
import 'package:vs_score_app/common/const/data.dart';
// ably realtime
final ablyRealtimeProvider = Provider<Realtime>(
(ref) {
final realtime = Realtime(
options: ClientOptions(
key: ABLYAPIKEY,
autoConnect: true,
disconnectedRetryTimeout: 5000,
suspendedRetryTimeout: 5000,
fallbackRetryTimeout: 5000,
channelRetryTimeout: 5000,
logLevel: LogLevel.verbose,
),
);
realtime.connection.on(ConnectionEvent.disconnected).listen((event) {
realtime.connection.connect();
});
realtime.connection.on(ConnectionEvent.suspended).listen((event) {
realtime.connection.connect();
});
realtime.connection.on(ConnectionEvent.failed).listen((event) {
realtime.connection.connect();
});
realtime.connection.on(ConnectionEvent.closed).listen((event) {
realtime.connection.connect();
});
return realtime;
},
);
This is my source code, but it doesn't happen on ios, but on android the app doesn't connect ably when it comes back to foreground after about 5 hours in the background. I think it only happens on android, is there anyone similar to me?
@walkerJung thank you very much for the details. We are looking into this. Right now, we still have difficulties reproducing the issue. Could you also share the Android version that you are using? We'll keep you posted as soon as we find something
@ttypic Thank you so much for answering, my test android phone's Android version is 13
Should I deliver the id value (realtime.connection.id ) of the realtime object to the recoverKey value? There were times when Error code : 80008 was shown. Maybe it's because the new connection is connected, but my app still refers to the last connection?
Hi @walkerJung you don't need to set realtime.connection.id
. Also, you don't need to handle reconnection explicitly. Our SDK clients automatically detects and handles reconnection. You can provide clientOption to provide reconnect timeout using disconnectedRetryTimeout
and suspendedRetryTimeout
.
hi @sacOO7 Thank you for your answer, then I don't need this part in my source code?
realtime.connection.on(ConnectionEvent.disconnected).listen((event) {
realtime.connection.connect();
});
realtime.connection.on(ConnectionEvent.suspended).listen((event) {
realtime.connection.connect();
});
realtime.connection.on(ConnectionEvent.failed).listen((event) {
realtime.connection.connect();
});
realtime.connection.on(ConnectionEvent.closed).listen((event) {
realtime.connection.connect();
});
Yes, you don't need to do it as such!
Failed
state is reached, when server closes connection due to some type of auth
error or when invalid token is provided. Closed
state is reached when you explicitly close the connection using realtime.close()
method call. Both of those states will never reach, if auth mechanism is correct and no explicit close call is provided. Let me go through the whole thread first, I might be able to help you a bit. There is auto retry mechanism for disconnected
and suspended
states, you just need to provide timeout values as a part of clientOptions.
@sacOO7 Then I guess there will be an error in the part where I use realtime as a provider, but how should I create a realtime object if I use about realtime on multiple screens and attach different channels on each screen? Now, I have made it a provider so that I can use it like singleton to create only one realtime object on one app
Yeah, I am familiar with a concept of providers in android. I can give you few suggestions on this. You should check for
You can also check official doc. on state management https://docs.flutter.dev/data-and-backend/state-mgmt/simple
I feel problem with using Providers
is that they are tightly bound to app lifecycle and hence app going foreground or background can affect them depending on the android or ios platform. If Providers doesn't work as expected, I would recommend using https://pub.dev/packages/flutter_background_service which runs in a different isolate. It might need extra permissions depending on the platform but will make sure your app will have a dedicated background service which doesn't depend on app lifecycle as such.
Your app being a chat app, it will be necessary to receive notifications even if app is closed
Or you can choose to run background service only when app is foreground/background and kill the service when app is closed.
I think your advice will be very helpful. I will refer to the linked content carefully and modify the realtime object I made as a provider. Thank you very much! @sacOO7
I changed the realtime object from provider to global variable and it seems the same thing happens on Android
import 'package:ably_flutter/ably_flutter.dart';
import 'package:vs_score_app/common/const/data.dart';
Realtime? globalRealtime;
void initializeGlobalRealtime() {
globalRealtime = Realtime(
options: ClientOptions(
key: ABLYAPIKEY,
autoConnect: true,
disconnectedRetryTimeout: 5000,
suspendedRetryTimeout: 5000,
fallbackRetryTimeout: 5000,
channelRetryTimeout: 5000,
logLevel: LogLevel.verbose,
),
);
}
Providers
are supposed to be global object tied to global application context ( at least in android ). Can you check how it transpiles in case of android. It should ideally be bound at application state level. A subclass of Application is responsible for maintaining all global objects. If your provider lives inside Activity
context ( given visible layout ), then it will not work when u switch to other layouts or app goes background.
Can you check if you can initialize Provider
inside FlutterApplication class as per -> https://stackoverflow.com/a/49084400/7363205
I'm not using a provider right now,
// realtime.dart
import 'package:ably_flutter/ably_flutter.dart';
import 'package:vs_score_app/common/const/data.dart';
Realtime? globalRealtime;
void initializeGlobalRealtime() {
globalRealtime = Realtime(
options: ClientOptions(
key: ABLYAPIKEY,
autoConnect: true,
disconnectedRetryTimeout: 5000,
suspendedRetryTimeout: 5000,
fallbackRetryTimeout: 5000,
channelRetryTimeout: 5000,
logLevel: LogLevel.verbose,
),
);
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
initializeGlobalRealtime();
await initializeAppSettings();
await initFCM();
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]).then(
(_) {
initializeDateFormatting().then(
(_) => runApp(
const ProviderScope(
child: _App(),
),
),
);
},
);
}
Create a realtime object by calling initializeGlobalRealtime() within the main method
void initializeAbly() async {
final realtime = globalRealtime;
chatChannel = realtime!.channels.get('SCORE:CHAT:MAIN');
subscription = chatChannel
.subscribe(names: ['system', 'punishment', 'chat', 'change']).listen(
(ably.Message message) {
if (message.data is Map) {
var chat = (message.data as Map).cast<String, dynamic>();
print("전체 채팅 메세지 도착: ${chat['chat']['message']}");
final meState = ref.read(meProvider);
// 채팅 삭제
if (chat['type'] == 'deleted_message') {
setState(() {
_messages.removeWhere(
(item) => item['chat']?['chatId'] == chat['chat']['chatId']);
});
return;
}
// 채팅 신고 5회시
if (chat['type'] == 'report5') {
for (var item in _messages) {
if (item['chat']?['chatId'] == chat['chat']['chatId']) {
setState(() {
item['chat']['message'] = chat['chat']['message'];
});
break;
}
}
return;
}
// 차단한 사람의 메세지 (방장은 차단 여부와 별개로 모두 볼수있음)
if (meState is ResponseModel &&
meState.data!['myBlocks']
.contains(chat['chat']?['profile']?['uid'])) {
return;
}
// 스크롤이 0~80 사이에 메세지가 오는 경우
if (_scrollController.hasClients) {
double maxOffset = _scrollController.position.maxScrollExtent;
double currentOffset = _scrollController.offset;
double eightyPercentOfMaxOffset = maxOffset * 0.2;
if (chat['type'] == 'normal' &&
currentOffset >= eightyPercentOfMaxOffset &&
_scrollController.position.maxScrollExtent > 0) {
_bottomMessage = message.data;
}
}
// 메세지 렌더링
setState(() {
_messages.insert(0, message.data);
});
}
});
}
I'm using it like this where I need realtime
How can I know? It's connected to a new connection. What should I do if I get a new connection?
You can check the documentation -> https://ably.com/docs/connect/states?lang=java
final ablyRealtime = AblyRealtimeSingleton().realtime;
print(ablyRealtime.connection.key);
Do I have to transfer the key value here to recover to keep the connection? It comes out as a key value null.
As a part of new flutter release, we will have a method called createRecoveryKey
on connection. It returns string
value. You can pass it as a recover
option to recover the connection.
So that method is not available now?
I make and use one realtime on the source code, but I don't understand that the app is disconnected when it goes to the background only on Android and then comes back to the foreground after a certain amount of time.
My app doesn't need to be connected even in the background. When the app returns to the foreground, there is also a logic to add observer to bring back data, so for me, only about connection needs to be connected and operated normally, but I don't know why that part isn't working
createRecoveryKey
method will be added as a part of https://github.com/ably/ably-flutter/pull/508
Interesting, you might like to take a look at https://developer.android.com/training/monitoring-device-state/doze-standby#understand_doze.
You might like to test your app in doze mode https://developer.android.com/training/monitoring-device-state/doze-standby#assessing_your_app. It will be helpful if we can reproduce the issue.
Is this a bug that only happens to me?
Not sure, it seems @ttypic is not able to reproduce the same from his side. Maybe, it has do with the way flutter_riverpod.Provider
handles realtime object when app goes background. But, if system is dropping network connection because it goes into doze
mode, then you need to reconnect after it comes online. I think there might be cases where client will not retry when system abruptly closes the connection ( it would be great, if we are able to find the case by reproducing the issue )
I have now changed the source code to disable the riversepod provider, but the same thing is still happening
Hey, can you do one thing? Upload this buggy flutter source code as a example github repo. I can clone and will try to reproduce the same from my side. Also are you using emulator for this ?
I'm testing with an actual Android device, you're saying I need a full source code that I can build right away?
Yes, are you able to reproduce the same on android emulator?
@sacOO7
Thanks to your help, I think using provider was a big problem. Apart from this, I have another question, and I wonder why the above source doesn't work. When the app goes to the background and comes back, it shows loader if it's not connected, and when it's connected, it clears loader, but the loader keeps showing up even after it's actually connected
final ablyRealtime = AblyRealtimeSingleton().realtime;
isAblyConnected =
ablyRealtime.connection.state == ably.ConnectionState.connected;
ablyRealtime.connection.on().listen((event) {
print("1111:::$event");
if (event.current == ably.ConnectionState.connected) {
isAblyConnected = true;
} else {
isAblyConnected = false;
}
setState(() {});
});
@walkerJung is your issue resolved ( apart from loader issue ) ? What did you do differently to implement the same ?
About loader issue, can you try declaring _isAblyConnected
as a state variable, that is updated inside
setState(() {
_isAblyConnected = isAblyConnected;
});
https://api.flutter.dev/flutter/widgets/State/setState.html https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html
The part surrounding the realtime with a provider was simply transformed into a single tone class and used.
How many seconds do you usually set the values of disconnected RetryTimeout, suspendedRetryTimeout, fallbackRetryTimeout, and channelRetryTimeout? Is it okay to set it to 3000ms?
You can set it according to your requirement. But, default values from disconnectedRetryTimeout
and suspendedRetryTimeout
are 15s and 30s respectivly
Are there any problems when you set it shorter than 3 seconds or 3 seconds?
Yes, you can set and test the same. Btw, ably-client
working as expected now ?
Yes, it's working normally. Your help is really good. I think the problem was with using the provider
Hi, I'm using flutter ably sdk. When testing on Android and IOS devices, when the app goes to the background and comes back to the foreground, there are times when the channel is not attached, but when the app comes back to the foreground, should I manage the realtime and channel?
┆Issue is synchronized with this Jira Bug by Unito