Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

flutter web - dio bloc's (freezes) ui while getting json response #961

Closed
kele82 opened this issue Oct 14, 2020 · 33 comments
Closed

flutter web - dio bloc's (freezes) ui while getting json response #961

kele82 opened this issue Oct 14, 2020 · 33 comments

Comments

@kele82
Copy link

kele82 commented Oct 14, 2020

Using default flutter project simple dio.get freezes CircularProgressIndicator while receiving response.
Tested first on iphone and ipad, works fine.
Then converted project to flutter web.

//code
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {

// This widget is the root of your application.
@OverRide
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(

    primarySwatch: Colors.blue,
  ),
  home: MyHomePage(title: 'Flutter Demo Home Page'),
);

}
}

class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);

final String title;

@OverRide
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State {
int _counter = 1;

void _incrementCounter() async {

setState(() {
  _counter++;
});

if (_counter % 2 == 0){
  try {
    print("Call api");
    var dio = Dio();


    Response response = await dio.get('https://antara.oxyproductservices.com/all');

    if(response.statusCode == 200) {
      print('Status ok');
      print(response.data);
    }else{
      print('ERROR -- ${response.statusCode}');
    }
  }catch(error){
    print('Dio error: $error');
  }

}

}

@OverRide
Widget build(BuildContext context) {

return Scaffold(
  appBar: AppBar(
    title: Text(widget.title),
  ),
  body: Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Text(
          'You have pushed the button this many times:',
        ),
        Text(
          '$_counter',
          style: Theme.of(context).textTheme.headline4,
        ),
        _presentProgress(),
      ],
    ),
  ),
  floatingActionButton: FloatingActionButton(
    onPressed: _incrementCounter,
    tooltip: 'Increment',
    child: Icon(Icons.add),
  ), // This trailing comma makes auto-formatting nicer for build methods.
);

}

_presentProgress() {
if (_counter % 2 == 0){
return CircularProgressIndicator();
}else{
return Text('Press (+) to send Api request');
}
}

}

@LucaIaconelli
Copy link

same problem here

@Oualitsen
Copy link

Oualitsen commented Nov 25, 2020

Same problem.
One small addition here.
I tested posting the same request with standard http in flutter it works as expected and does not hang.

@stale
Copy link

stale bot commented Dec 25, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If this is still an issue, please make sure it is up to date and if so, add a comment that this is still an issue to keep it open. Thank you for your contributions.

@stale stale bot added the stale label Dec 25, 2020
@despie
Copy link

despie commented Dec 29, 2020

This is still an issue in dio: ^3.0.10
And this holds true not only for the web, but also for both iOS & Android.
As soon as the data starts to load, the UI becomes unresponsive for a while.

@stale stale bot removed the stale label Dec 29, 2020
@Oualitsen
Copy link

Oualitsen commented Dec 29, 2020

This worked for me for the Web
The issue was actually using functional transformations to the request body. When the request body if a long byte array the UI just freezes.
I tried to come out with so many theories (Java-wise) with multi-threading but ended up changing the functional transformation to a for loop.
Please check out the code and let me know if it works for you.
`
class WebClientAdapter implements HttpClientAdapter {
final _xhrs = <html.HttpRequest>[];

bool withCredentials = false;

@OverRide
Future fetch(
RequestOptions options, Stream<List> requestStream, Future cancelFuture) async {
var xhr = html.HttpRequest();
_xhrs.add(xhr);

xhr
  ..open(options.method, options.uri.toString(), async: true)
  ..responseType = 'blob'
  ..withCredentials = options.extra['withCredentials'] ?? withCredentials;
options.headers.remove(Headers.contentLengthHeader);
options.headers.forEach((key, v) => xhr.setRequestHeader(key, '$v'));
var completer = Completer<ResponseBody>();

xhr.onLoad.first.then((_) {
  var blob = xhr.response ?? html.Blob([]);
  var reader = html.FileReader();

  reader.onLoad.first.then((_) {
    var body = reader.result as Uint8List;

    completer.complete(
      ResponseBody.fromBytes(
        body,
        xhr.status,
        headers: xhr.responseHeaders.map((k, v) => MapEntry(k, v.split(','))),
        statusMessage: xhr.statusText,
        isRedirect: xhr.status == 302 || xhr.status == 301,
      ),
    );
  });

  reader.onError.first.then((error) {
    completer.completeError(
      DioError(
        type: DioErrorType.RESPONSE,
        error: error,
        request: options,
      ),
      StackTrace.current,
    );
  });

  reader.readAsArrayBuffer(blob);
});

xhr.onError.first.then((_) {
  completer.completeError(
    DioError(
      type: DioErrorType.RESPONSE,
      error: 'XMLHttpRequest error.',
      request: options,
    ),
    StackTrace.current,
  );
});

cancelFuture?.then((_) {
  if (xhr.readyState < 4 && xhr.readyState > 0) {
    try {
      xhr.abort();
    } catch (e) {
      // ignore
    }
  }
});

if (requestStream == null) {
  xhr.send();
} else {
  List<int> r = [];
  List<List<int>> listOfLists = await requestStream.toList();
  for (int i = 0; i < listOfLists.length; i++) {
    r.addAll(listOfLists[i]);
  }
  xhr.send(Uint8List.fromList(r));
}
return completer.future.whenComplete(() {
  _xhrs.remove(xhr);
});

}

@OverRide
void close({bool force = false}) {
if (force) {
for (var xhr in _xhrs) {
xhr.abort();
}
}
_xhrs.clear();
}
}
`

@Oualitsen
Copy link

This is still an issue in dio: ^3.0.10
And this holds true not only for the web, but also for both iOS & Android.
As soon as the data starts to load, the UI becomes unresponsive for a while.

Checkout My solution above and I hope it'll work for you too on the web.

@rajivrajan
Copy link

A simpler solution is to understand what 'await' does. The reason your requests are getting queued, is because await explicitly asks that of the framework. the solution would be to use

dio.get('https://antara.oxyproductservices.com/all').then((result){
   add(ApiResponseEvent(result));
}).catchError((error){
 //catch and handle error
});

@kele82
Copy link
Author

kele82 commented Feb 10, 2021

@maicolstracci
Copy link

Same problem here

@arthurdlima
Copy link

I'm still having this problem on flutter web. Any solutions?

@moniaS
Copy link

moniaS commented Mar 1, 2021

Same problem here

@altafkhan8719
Copy link

same problem here , it taking 3 to 4 second to load data,
same URL on postman it take 500 to 600 millisecond.
please provide a solution

@Oualitsen
Copy link

Salam @altafkhan8719
Here is the solution I adopted after hours of googling
Below is a modified version of web adapter.

`import 'dart:async';
import 'dart:typed_data';
import 'package:dio/dio.dart';
import 'package:universal_html/html.dart' as html;

class WebClientAdapter implements HttpClientAdapter {
final _xhrs = <html.HttpRequest>[];

bool withCredentials = false;

@OverRide
Future fetch(
RequestOptions options, Stream<List> requestStream, Future cancelFuture) async {
var xhr = html.HttpRequest();
_xhrs.add(xhr);

xhr
  ..open(options.method, options.uri.toString(), async: true)
  ..responseType = 'blob'
  ..withCredentials = options.extra['withCredentials'] ?? withCredentials;
options.headers.remove(Headers.contentLengthHeader);
options.headers.forEach((key, v) => xhr.setRequestHeader(key, '$v'));
var completer = Completer<ResponseBody>();

xhr.onLoad.first.then((_) {
  var blob = xhr.response ?? html.Blob([]);
  var reader = html.FileReader();

  reader.onLoad.first.then((_) {
    var body = reader.result as Uint8List;

    completer.complete(
      ResponseBody.fromBytes(
        body,
        xhr.status,
        headers: xhr.responseHeaders.map((k, v) => MapEntry(k, v.split(','))),
        statusMessage: xhr.statusText,
        isRedirect: xhr.status == 302 || xhr.status == 301,
      ),
    );
  });

  reader.onError.first.then((error) {
    completer.completeError(
      DioError(
        type: DioErrorType.RESPONSE,
        error: error,
        request: options,
      ),
      StackTrace.current,
    );
  });

  reader.readAsArrayBuffer(blob);
});

xhr.onError.first.then((_) {
  completer.completeError(
    DioError(
      type: DioErrorType.RESPONSE,
      error: 'XMLHttpRequest error.',
      request: options,
    ),
    StackTrace.current,
  );
});

cancelFuture?.then((_) {
  if (xhr.readyState < 4 && xhr.readyState > 0) {
    try {
      xhr.abort();
    } catch (e) {
      // ignore
    }
  }
});

if (requestStream == null) {
  xhr.send();
} else {
  List<int> r = [];
  List<List<int>> listOfLists = await requestStream.toList();
  for (int i = 0; i < listOfLists.length; i++) {
    r.addAll(listOfLists[i]);
  }
  xhr.send(Uint8List.fromList(r));
}
return completer.future.whenComplete(() {
  _xhrs.remove(xhr);
});

}

@OverRide
void close({bool force = false}) {
if (force) {
for (var xhr in _xhrs) {
xhr.abort();
}
}
_xhrs.clear();
}
}
In the initialization of my web services I use dio with this web adapter like belowFuture initDio() async {
GetIt instance = GetIt.instance;
Dio dio = Dio(BaseOptions(baseUrl: getUrlBase()));

if (kIsWeb) {
CookieManager manager = CookieManager(CookieJar());
dio.interceptors.add(manager);

---------------------> dio.httpClientAdapter = WebClientAdapter();
} else {

}

}`

@xellDart
Copy link

same error

@giaur500
Copy link

giaur500 commented May 7, 2021

Is that web only issue? On Android/iOS I don't get any difference, both default http client and dio give the same load time and no freezes. I use dio to make simple rest calls, but response may be large in some cases. Overall, response and load time is similar to default http client and native Android OkHttp with gson.

@livotov
Copy link

livotov commented May 7, 2021

@giaur500 I just quickly checked with Android on the same endpoint/dataset - it seems it happens there too. Not as badly as for web - ~1.5 sec complete UI freeze (Android) vs 15 sec freeze on web. But freeze still happens. Just not noticeable on smaller datasets in comparison to web, I suppose.

@giaur500
Copy link

giaur500 commented May 7, 2021

Do you use isolates (flutter compute) to parse json? I see no freezes and response size is ~400 kB. Dio version 4.0.0, but 3.10 was ok too as far as I remember.

GET request call +parse + finally appear result in gui takes approx 0.5 to max 1 second. On native Android it's not better. Other thing that can make difference - http compression is enabled on server.

@livotov
Copy link

livotov commented May 7, 2021

No isolates. Just Retrofit + Dio. My response is 1.5 MB in size, could it make difference?

there is an "update in progress" spinner in appbar of my app, so it is clearly visible:

  • "refresh" button pressed
  • progress indicator starts spinning, spins for about 5 to 7 seconds - this is transmission time for 1.5 MB
  • then it freezes (entire UI too - I cannot swipe pages or list) for 1 to 1.5 sec
  • then data gets updated, everything unfreezes

simply replacing Retrofit/Dio call to HTTP client one + manual obj.fromJson(...) - gets same 5-7 secs timing for the network but no any freeze then. The timing for obj.fromJson(jsonDecode(...)) for this payload is only 157 ms

so it seems this functional transformation of the request body "eats" the main thread (even if run in isolate) and this most noticeably affects web targets. Dunno whether this is a language/compiler or flutter issue or not.

@giaur500
Copy link

giaur500 commented May 7, 2021

Try to use Dio (without Retrofit) to see what happens. Also, you should notice Dio has its defualt transformer for json, maybe it causes freezes?

I use raw response this way:
Response<String> response = await dio.get(....) so I guess no transformation is used in this case and dio performs the same work as default http client does - response.data contains server response without any transformation.

@stale
Copy link

stale bot commented Jun 11, 2021

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If this is still an issue, please make sure it is up to date and if so, add a comment that this is still an issue to keep it open. Thank you for your contributions.

@stale stale bot added the stale label Jun 11, 2021
@maares
Copy link

maares commented Jun 15, 2021

Hi guys, I'm also interested in this particular subject. Making a request with Dio on Flutter Web freezes completely the UI. After 2 days without any elegant solution to the problem, decided to switch request client for certain api calls with http. Literally no issues when switching.

Has anyone come up with a solution?
Thanks in advance

@stale stale bot removed the stale label Jun 15, 2021
@stale
Copy link

stale bot commented Jul 20, 2021

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If this is still an issue, please make sure it is up to date and if so, add a comment that this is still an issue to keep it open. Thank you for your contributions.

@stale stale bot added the stale label Jul 20, 2021
@stale stale bot closed this as completed Jul 30, 2021
@Mooibroek
Copy link

Still having this in the latest version of dio (4.0.0). I tried using an isolate to do the json_decode but still a good bit of jank after the response comes in. If needed I can provide a test project to simulate this behaviour. Anyone else got a viable solution to the problem?

@maares
Copy link

maares commented Sep 2, 2021

@Mooibroek unfortunatelly the only work around at least for me was to make request with different client for web, mobile works good, but probably will finally migrate all to only one request client in order not to have extra logic by using 2 clients. :(

@Mooibroek
Copy link

I've got the problem on macos now, ill try the mobile and see how that goes.

@livotov
Copy link

livotov commented Sep 2, 2021

Just FYI - we completely abandoned both DIO and Retrofit due to that.
We still use json_serializable for mapping complex JSON objects and for the rest we do use the plain HTTP client. It does not add much boilerplate and remains flexible.

@livotov
Copy link

livotov commented Sep 2, 2021

@maares mobile works faster (probably due to native code) but is affected by the same issue - the lag is just much smaller in comparison to the web. So, IMO, no need to use 2 clients, just move away from DIO/Retrofit. As I mentioned above - standard HTTP package syntax is quite nice and simple, still flexible

@Mooibroek
Copy link

Yeah lag on mobile is also there, but less then on macos. Shame really. For now its not a deal breaker for this project, but definitely something to take into account for new projects in the future.

@giaur500
Copy link

giaur500 commented Sep 2, 2021

How do you use basic http client on web? In my case no matter what I do, I'm getting exception "Unsupported operation: Platform._version", even if I do what they suggested, exception is still there.

@livotov
Copy link

livotov commented Sep 2, 2021

This is the direct snippet from our app, just anonymized sensitive data.

import 'package:http/http.dart' as http;

final client = http.Client();

final responseJsonString = await client.post(
        Uri.parse('https://xxxx.com/api/v1/method'),
        headers: {
          'Authorization': 'Bearer $token',
          'Content-type': 'application/json'
        },
        body: utf8.encode(json.encode({
          'userId': userId,
          'locationId': locationId
        })));

@maares
Copy link

maares commented Sep 2, 2021

@livotov thanks for actually observing lag is on mobile as well, honestly I did not notice on Mobile. A shame, this was actually one of the first packages I installed when starting playing around with flutter.

Hopefully at some point this will be addressed

@saucelooker
Copy link

Hi all. Perhaps for some this is still relevant. I had the same problem - the interface froze when there were heavy replies. My problem was that I had a log interceptor that was writing a lot of data to the console. Check if you have the same interceptors or just print(some data) with a large amount of data. This could be one of the problems.

@bayufajariyanto
Copy link

I encountered a similar issue, but I managed to solve it by simply adding validateStatus to my Dio configuration:

Dio(BaseOptions(
  baseUrl: Urls.base,
  headers: {
    'Content-Type': 'application/json',
  },
  connectTimeout: const Duration(seconds: 2),
  receiveTimeout: const Duration(seconds: 2),
  validateStatus: (status) => status! >= 200 && status < 500,
));

With validateStatus, I allow Dio to treat all responses with status codes from 200 to 499 as valid, preventing unnecessary exceptions for client errors (like 400 or 401), which I can then handle within my application.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests