How to close stream on app shutdown when created in main? - flutter

I have a custom http client that intercepts responses to provide service unavailable events via a stream:
class MyHttpClient extends http.BaseClient {
final String apiBase;
MyHttpClient({required this.apiBase});
final _controller = StreamController<ServiceUnavailableEvent>.broadcast();
Stream<ServiceUnavailableEvent> get stream => _controller.stream;
#override
Future<http.StreamedResponse> send(http.BaseRequest request) async {
final response = await request.send();
if (request.url.toString().startsWith(apiBase) &&
response.statusCode == HttpStatus.serviceUnavailable) {
_controller.sink.add(ServiceUnavailableEvent(isAvailable: false));
} else {
_controller.sink.add(ServiceUnavailableEvent(isAvailable: true));
}
return response;
}
Future<void> close() async {
await _controller.close();
}
}
The client is instanciated once in the main flutter function:
void main() {
final client = MyHttpClient(apiBase: apiBase)
runApp(MyApp(client: client));
}
Everything works fine and I added the close method on the custom http client that closes the stream. But when/how can I call this close method ?
I thought about AppLifeCycleEvent but all state seem to be the wrong place, because I only want to close the client when the app really shuts down (i.e. if the user re-opens/resumes the app a new client must be created in order to recreate the subscription).

how do you know it is still open when app shutdown. i think platform(ios/android) close it.

Related

How to handle stream in the background?

I'm requesting a server and server responses a Stream. The first element in the stream is the data updates the UI but other Stream events needs to be in the background. I have to return stream.first back in order to update the state.
Future<AccountResponse> cEAccount(Account request) async {
final stream = client.cEAccount(request).asBroadcastStream();
final firstRes = await stream.first;
watchBlockchainResponse(stream);
return firstRes;
}
Future watchBlockchainResponse(Stream stream) async {
await for (final res in stream) {
...
}
watchBlockchainResponse shows an overlay using overlay_support. This code is working fine. Is this a good practice? If not, how should I handle this?
You can use
stream.listen((res) {
});

Why is this listener never called?

I'm trying to use Riverpod for my project, however I'm hitting some issues.
I am not sure that I'm using it very well so don't hesitate to tell me if you see anything wrong with it :)
First I have my authProvider:
final authRepoProvider = ChangeNotifierProvider.autoDispose((ref) {
return AuthRepository();
});
class AuthRepository extends ChangeNotifier {
String? token;
Future signIn(String username, String password) async {
// Do the API calls...
token = tokenReturnedByAPI;
notifyListeners();
}
}
Then I have a service, let's say it allows to fetch blog Articles, with a stream to get live update about those.
class ArticleService {
StreamController<Article> _streamCtrl;
String? _token;
API _api;
ArticleService(this._api) : _streamCtrl = StreamController<Article>() {
_api.onLiveUpdate((liveUpdate) {
_streamCtrl.add(liveUpdate);
});
}
Stream<Article> get liveUpdates => _streamCtrl.stream;
Future markArticleAsRead(String id) async {
await _api.markAsRead(_token, id);
}
}
For that article service I would like to keep the current token up to date, but I don't want to rebuild the entire service every time the token changes as there are listeners and streams being used.
For that I would prefer to listen to the changes and update it myself, like such:
final articleServiceProvider = Provider.autoDispose((ref) {
final service = ArticleService(
ref.read(apiProvider),
);
ref.listen<AuthRepository>(authRepositoryProvider, (previous, next) {
service._token = next.token;
}, fireImmediately: true);
return service;
});
That piece of code seems correct to me, however when I authenticate (authRepository.token is definitely set) and then try to invoke the markArticlesAsRead method I end up with an empty token.
The ref.listen is never called, even tho AuthRepository called notifyListeners().
I have a feeling that I'm using all that in a wrong way, but I can't really pinpoint what or where.
Try ref.watch
final articleServiceProvider = Provider.autoDispose((ref) {
final service = ArticleService(
ref.read(apiProvider),
);
final repo = ref.watch<AuthRepository>(authRepositoryProvider);
service._token = repo.token;
return service;
});

Flutter uni_links duplicate the app every time a link is clicked

I am implementing a password recovery function based on the url sent to the email. Opening the app based on that url was successful. But instead of directly opening the required page in the app that is in the background, it duplicates the app. Although it still leads me to the password recovery page, now there will be 2 same apps running side by side
Procedure
Enter your email to send the password reset link
Click submit
Open the email containing the recovery link
Duplicate the app and open a recovery password page
Things what happen
Splash screen, first page open in the app, I am trying to do as instructed from uni_links package but still no success. Currently the function getInitialLink has the effect of opening the app based on the recovery link
class SplashController extends GetxController {
final SharedPreferencesHelper _helper = Get.find<SharedPreferencesHelper>();
late StreamSubscription sub;
#override
void onReady() async {
super.onReady();
await checkToken();
}
Future<void> checkToken() async {
await Future.delayed(Duration(seconds: 3));
var token = _helper.getToken();
if (token == null) {
Get.offNamed(Routes.LOGIN);
} else {
Get.offNamed(Routes.MAIN);
}
}
#override
void onInit() {
super.onInit();
initUniLinks();
}
Future<Null> initUniLinks() async {
// Platform messages may fail, so we use a try/catch PlatformException.
try {
String? initialLink = await getInitialLink();
if (initialLink != null) {
print("okay man");
Get.toNamed(Routes.RECOVERY);
}
sub = getLinksStream().listen((link) {
}, onError: (err) {
});
} on PlatformException {
// Handle exception by warning the user their action did not succeed
// return?
}
}
}
I found the solution, actually this answer is already on Stackoverflow, and it's really simple.
In the AndroidManifest.xml file of the app. Find "android:launchMode" and change its old value to singleTask. And here is the result
android:launchMode="singleTask"

How to use http interceptor in a flutter project?

I have to add header to all my Api's. I was told to use http interceptor for that. But i am not able to understand how to do it as i am new to flutter. Can anyone help me with example?
you can use http_interceptor.
it works as follows,
first you create your interceptor by implementing InterceptorContract
class MyInterceptor implements InterceptorContract {
#override
Future<RequestData> interceptRequest({RequestData data}) async {
try {
data.headers["Content-Type"] = "application/json";
} catch (e) {
print(e);
}
return data;
}
#override
Future<ResponseData> interceptResponse({ResponseData data}) async => data;
}
then create a client and inject this interceptor in it
Client _client = InterceptedClient.build(interceptors: [
MyInterceptor(),
]);
You can add multiple interceptors to the same client, say you want one to refresh the token, one to add/change headers, so it will be something like this:
Client _client = InterceptedClient.build(interceptors: [
RefreshTokenInterceptor(),
ContentTypeInterceptor(),
/// etc
]);
note that every interceptor must implement the InterceptorContract
Now anytime you use this client, the request will be intercepted and headers will be added to it. You can make this client a singleton to use the same instance across the app like this
class HttpClient {
Client _client;
static void _initClient() {
if (_client == null) {
_client = InterceptedClient.build(
interceptors: [MyInterceptor()],
);
}
}
/// implement http request with this client
}

Retry Http Get request if there is no response in Flutter

getData() async {
http.Response response = await http.get('https://www.example.com/);
print(response.body);
}
The above function works to get the HTML code of a page but it fails in some cases. The function is sometimes never completed and it waits forever to get response( For example, if the app is opened while internet is off and even when its turned on, it never connects). In such situations is there any way to retry ?
I tried the http retry package but it gives me 15+ errors.
Example code for how this could be done:
import 'package:http/http.dart' as http;
import 'dart:convert';
Future<List> loadData() async {
bool loadRemoteDatatSucceed = false;
var data;
try {
http.Response response = await http.post("https://www.example.com",
body: <String, String>{"username": "test"});
data = json.decode(response.body);
if (data.containsKey("success")) {
loadRemoteDatatSucceed = true;
}
} catch (e) {
if (loadRemoteDatatSucceed == false) retryFuture(loadData, 2000);
}
return data;
}
retryFuture(future, delay) {
Future.delayed(Duration(milliseconds: delay), () {
future();
});
}
You can use RetryPolicy from http package to retry your connection, just create your own class and inherit form RetryPolicy and override these function like the following example, then create a Client using HttpClientWithInterceptor.build and add your custom retryPolicy as a parameter, this will retry your request for a number of times until a condition is met, if not, it'll just stop retrying.
import 'package:http/http.dart';
class MyRetryPolicy extends RetryPolicy {
final url = 'https://www.example.com/';
#override
// how many times you want to retry your request.
int maxRetryAttempts = 5;
#override
Future<bool> shouldAttemptRetryOnResponse(ResponseData response) async {
//You can check if you got your response after certain timeout,
//or if you want to retry your request based on the status code,
//usually this is used for refreshing your expired token but you can check for what ever you want
//your should write a condition here so it won't execute this code on every request
//for example if(response == null)
// a very basic solution is that you can check
// for internet connection, for example
try {
final result = await InternetAddress.lookup('google.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
return true;
}
return false;
} on SocketException catch (_) {
return false;
}
}
}
then create and use a client to make your requests.
it will automatically retry the request if the condition you wrote is met.
Client client = HttpClientWithInterceptor.build(
retryPolicy: ExpiredTokenRetryPolicy(),
);
final response = await client.get('https://www.example.com/);
there is also a package to check for internet connection if that your problem, see connectivity
You can use try-catch blocks inside async functions like you would in synchronous code. Perhaps you'd be able to add some sort of error handling mechanism in the function, and retry the function on error? Here's some documentation on that one.
Example from the docs:
try {
var order = await getUserOrder();
print('Awaiting user order...');
} catch (err) {
print('Caught error: $err');
}
You can also catch specific Exceptions, per this github issue.
doLogin(String username, String password) async {
try {
var user = await api.login(username, password);
_view.onLoginSuccess(user);
} on Exception catch(error) {
_view.onLoginError(error.toString());
}
}
EDIT: This may also help.
While we're at it, look here for a function that reattempts an async operation however many times you need.