flutter / flutter

Flutter makes it easy and fast to build beautiful apps for mobile and beyond
https://flutter.dev
BSD 3-Clause "New" or "Revised" License
165.16k stars 27.24k forks source link

Http Future never completes #64391

Closed dario-digregorio closed 4 years ago

dario-digregorio commented 4 years ago

Steps to Reproduce

  1. Run flutter create bug.
  2. Update the files as follows: main.dart
    
    import 'package:flutter/material.dart';
    import 'package:flutter_app/weather_model.dart';
    import 'package:flutter_app/weather_service.dart';
    import 'package:http/http.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, visualDensity: VisualDensity.adaptivePlatformDensity, ), 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 { final HttpService _httpService = HttpService(); Future _weatherLocation;

@override void initState() { super.initState(); _weatherLocation = _httpService.getWeatherLocation(Client()); }

@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: FutureBuilder( future: _weatherLocation, builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasData) { print("hi"); return Container(); } else { return Center(child: CircularProgressIndicator()); } }, )); } }


**weather_service.dart**
```dart
import 'dart:convert';

import 'package:flutter/foundation.dart';
import 'package:flutter_app/weather_model.dart';
import 'package:http/http.dart';

class HttpService {
  final String weatherURL =
            'https://api.openweathermap.org/data/2.5/weather?lat=12.8578252&units=metric&lon=66.0999831&appid=<API_KEY>';

  Future<WeatherLocation> getWeatherLocation(Client client) async {
    Response res = await client.get(weatherURL);

    return compute(parseWeatherLocation, res.body);
  }

  WeatherLocation parseWeatherLocation(String json){
    return WeatherLocation.fromJson(jsonDecode(json));
  }
}

pubspec.yaml

dependencies:
  http: ^0.12.2
  flutter:
    sdk: flutter
weather_model.dart which I generated with an online tool class WeatherLocation { double lat; double lon; String timezone; int timezoneOffset; Current current; List minutely; List hourly; List daily; WeatherLocation( {this.lat, this.lon, this.timezone, this.timezoneOffset, this.current, this.minutely, this.hourly, this.daily}); WeatherLocation.fromJson(Map json) { lat = json['lat']; lon = json['lon']; timezone = json['timezone']; timezoneOffset = json['timezone_offset']; current = json['current'] != null ? new Current.fromJson(json['current']) : null; if (json['minutely'] != null) { minutely = new List(); json['minutely'].forEach((v) { minutely.add(new Minutely.fromJson(v)); }); } if (json['hourly'] != null) { hourly = new List(); json['hourly'].forEach((v) { hourly.add(new Hourly.fromJson(v)); }); } if (json['daily'] != null) { daily = new List(); json['daily'].forEach((v) { daily.add(new Daily.fromJson(v)); }); } } Map toJson() { final Map data = new Map(); data['lat'] = this.lat; data['lon'] = this.lon; data['timezone'] = this.timezone; data['timezone_offset'] = this.timezoneOffset; if (this.current != null) { data['current'] = this.current.toJson(); } if (this.minutely != null) { data['minutely'] = this.minutely.map((v) => v.toJson()).toList(); } if (this.hourly != null) { data['hourly'] = this.hourly.map((v) => v.toJson()).toList(); } if (this.daily != null) { data['daily'] = this.daily.map((v) => v.toJson()).toList(); } return data; } } class Current { int dt; int sunrise; int sunset; double temp; double feelsLike; int pressure; int humidity; double dewPoint; double uvi; int clouds; int visibility; double windSpeed; int windDeg; List weather; Current( {this.dt, this.sunrise, this.sunset, this.temp, this.feelsLike, this.pressure, this.humidity, this.dewPoint, this.uvi, this.clouds, this.visibility, this.windSpeed, this.windDeg, this.weather}); Current.fromJson(Map json) { dt = json['dt']; sunrise = json['sunrise']; sunset = json['sunset']; temp = json['temp']; feelsLike = json['feels_like']; pressure = json['pressure']; humidity = json['humidity']; dewPoint = json['dew_point']; uvi = json['uvi']; clouds = json['clouds']; visibility = json['visibility']; windSpeed = json['wind_speed']; windDeg = json['wind_deg']; if (json['weather'] != null) { weather = new List(); json['weather'].forEach((v) { weather.add(new Weather.fromJson(v)); }); } } Map toJson() { final Map data = new Map(); data['dt'] = this.dt; data['sunrise'] = this.sunrise; data['sunset'] = this.sunset; data['temp'] = this.temp; data['feels_like'] = this.feelsLike; data['pressure'] = this.pressure; data['humidity'] = this.humidity; data['dew_point'] = this.dewPoint; data['uvi'] = this.uvi; data['clouds'] = this.clouds; data['visibility'] = this.visibility; data['wind_speed'] = this.windSpeed; data['wind_deg'] = this.windDeg; if (this.weather != null) { data['weather'] = this.weather.map((v) => v.toJson()).toList(); } return data; } } class Weather { int id; String main; String description; String icon; Weather({this.id, this.main, this.description, this.icon}); Weather.fromJson(Map json) { id = json['id']; main = json['main']; description = json['description']; icon = json['icon']; } Map toJson() { final Map data = new Map(); data['id'] = this.id; data['main'] = this.main; data['description'] = this.description; data['icon'] = this.icon; return data; } } class Minutely { int dt; int precipitation; Minutely({this.dt, this.precipitation}); Minutely.fromJson(Map json) { dt = json['dt']; precipitation = json['precipitation']; } Map toJson() { final Map data = new Map(); data['dt'] = this.dt; data['precipitation'] = this.precipitation; return data; } } class Hourly { int dt; double temp; double feelsLike; int pressure; int humidity; double dewPoint; int clouds; int visibility; double windSpeed; int windDeg; List weather; double pop; Rain rain; Hourly( {this.dt, this.temp, this.feelsLike, this.pressure, this.humidity, this.dewPoint, this.clouds, this.visibility, this.windSpeed, this.windDeg, this.weather, this.pop, this.rain}); Hourly.fromJson(Map json) { dt = json['dt']; temp = json['temp']; feelsLike = json['feels_like']; pressure = json['pressure']; humidity = json['humidity']; dewPoint = json['dew_point'].toDouble(); clouds = json['clouds']; visibility = json['visibility']; windSpeed = json['wind_speed']; windDeg = json['wind_deg']; if (json['weather'] != null) { weather = new List(); json['weather'].forEach((v) { weather.add(new Weather.fromJson(v)); }); } pop = json['pop'].toDouble(); rain = json['rain'] != null ? new Rain.fromJson(json['rain']) : null; } Map toJson() { final Map data = new Map(); data['dt'] = this.dt; data['temp'] = this.temp; data['feels_like'] = this.feelsLike; data['pressure'] = this.pressure; data['humidity'] = this.humidity; data['dew_point'] = this.dewPoint; data['clouds'] = this.clouds; data['visibility'] = this.visibility; data['wind_speed'] = this.windSpeed; data['wind_deg'] = this.windDeg; if (this.weather != null) { data['weather'] = this.weather.map((v) => v.toJson()).toList(); } data['pop'] = this.pop; if (this.rain != null) { data['rain'] = this.rain.toJson(); } return data; } } class Rain { double d1h; Rain({this.d1h}); Rain.fromJson(Map json) { d1h = json['1h']; } Map toJson() { final Map data = new Map(); data['1h'] = this.d1h; return data; } } class Daily { int dt; int sunrise; int sunset; Temp temp; FeelsLike feelsLike; int pressure; int humidity; double dewPoint; double windSpeed; int windDeg; List weather; int clouds; double pop; double rain; double uvi; Daily( {this.dt, this.sunrise, this.sunset, this.temp, this.feelsLike, this.pressure, this.humidity, this.dewPoint, this.windSpeed, this.windDeg, this.weather, this.clouds, this.pop, this.rain, this.uvi}); Daily.fromJson(Map json) { dt = json['dt']; sunrise = json['sunrise']; sunset = json['sunset']; temp = json['temp'] != null ? new Temp.fromJson(json['temp']) : null; feelsLike = json['feels_like'] != null ? new FeelsLike.fromJson(json['feels_like']) : null; pressure = json['pressure']; humidity = json['humidity']; dewPoint = json['dew_point']; windSpeed = json['wind_speed']; windDeg = json['wind_deg']; if (json['weather'] != null) { weather = new List(); json['weather'].forEach((v) { weather.add(new Weather.fromJson(v)); }); } clouds = json['clouds']; pop = json['pop'].toDouble(); rain = json['rain']; uvi = json['uvi']; } Map toJson() { final Map data = new Map(); data['dt'] = this.dt; data['sunrise'] = this.sunrise; data['sunset'] = this.sunset; if (this.temp != null) { data['temp'] = this.temp.toJson(); } if (this.feelsLike != null) { data['feels_like'] = this.feelsLike.toJson(); } data['pressure'] = this.pressure; data['humidity'] = this.humidity; data['dew_point'] = this.dewPoint; data['wind_speed'] = this.windSpeed; data['wind_deg'] = this.windDeg; if (this.weather != null) { data['weather'] = this.weather.map((v) => v.toJson()).toList(); } data['clouds'] = this.clouds; data['pop'] = this.pop; data['rain'] = this.rain; data['uvi'] = this.uvi; return data; } } class Temp { double day; double min; double max; double night; double eve; double morn; Temp({this.day, this.min, this.max, this.night, this.eve, this.morn}); Temp.fromJson(Map json) { day = json['day']; min = json['min']; max = json['max']; night = json['night']; eve = json['eve']; morn = json['morn']; } Map toJson() { final Map data = new Map(); data['day'] = this.day; data['min'] = this.min; data['max'] = this.max; data['night'] = this.night; data['eve'] = this.eve; data['morn'] = this.morn; return data; } } class FeelsLike { double day; double night; double eve; double morn; FeelsLike({this.day, this.night, this.eve, this.morn}); FeelsLike.fromJson(Map json) { day = json['day']; night = json['night']; eve = json['eve']; morn = json['morn']; } Map toJson() { final Map data = new Map(); data['day'] = this.day; data['night'] = this.night; data['eve'] = this.eve; data['morn'] = this.morn; return data; } }

3.Just run the application and see that the Future never completes. I just followed the instruction on flutter.dev and wanted to try out the openweatherapi.

Expected results: The Future should complete and the CircularProgressIndicator should go away. Also the console should print 'hi' but it never does.

Actual results: CircularProgressIndicator is spinning forever. Nevertheless I can type in any url and it never completes. Not even an error. If I return the response instead so I dont parse anything the Future completes instead. But the parsing function is just a synchronized job so I am not sure what really produce this strange behavior. But since it is so easy to reproduce I created an Issue.

Console output

Launching lib\main.dart on Android SDK built for x86 in debug mode...
Running Gradle task 'assembleDebug'...
✓ Built build\app\outputs\flutter-apk\app-debug.apk.
Installing build\app\outputs\flutter-apk\app.apk...
Waiting for Android SDK built for x86 to report its views...
Debug service listening on ws://127.0.0.1:59843/9mzG9nb1Km0=/ws
Syncing files to device Android SDK built for x86...
I/rio.flutter_ap(10152): Background young concurrent copying GC freed 14647(5627KB) AllocSpace objects, 3(60KB) LOS objects, 84% free, 1157KB/7301KB, paused 3.399ms total 127.504ms
D/EGL_emulation(10152): eglMakeCurrent: 0xd4898180: ver 2 0 (tinfo 0xd7e0f7e0)

Flutter Analyze

flutter analyze
Analyzing flutter_app...                                                
No issues found! (ran in 2.7s)

Flutter Doctor

flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, 1.20.2, on Microsoft Windows [Version 10.0.19041.450], locale en-DE)

[√] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
[!] Android Studio (version 4.0)
    X Flutter plugin not installed; this adds Flutter specific functionality. // <-- both are installed and are working??
    X Dart plugin not installed; this adds Dart specific functionality.
[√] Connected device (1 available)

! Doctor found issues in 1 category.
dario-digregorio commented 4 years ago

I tested the app on different devices and emulators and all have the same result. When I call the link it returns the json.

Mashood97 commented 4 years ago

You need to put parseWeatherLocation method outside your weather Location Class as when using compute the method you pass for parsing must be static if use within in a class or must be outside the class to access the data. Weather Location class must be like this:

WeatherLocation parseWeatherLocation(String json){ return WeatherLocation.fromJson(jsonDecode(json)); } class HttpService { final String weatherURL = 'https://api.openweathermap.org/data/2.5/weather?lat=12.8578252&units=metric&lon=66.0999831&appid=';

Future getWeatherLocation(Client client) async { Response res = await client.get(weatherURL); return compute(parseWeatherLocation, res.body); } }

iapicca commented 4 years ago

@dario-digregorio compute is sugar for isolate which need to take a top level or static function I'm not sure that this is the case for parseWeatherLocation

does the code below run?

import 'dart:convert';

import 'package:flutter/foundation.dart';
import 'package:flutter_app/weather_model.dart';
import 'package:http/http.dart';

class HttpService {
  final String weatherURL =
            'https://api.openweathermap.org/data/2.5/weather?lat=12.8578252&units=metric&lon=66.0999831&appid=<API_KEY>';

  Future<WeatherLocation> getWeatherLocation(Client client) async {
    Response res = await client.get(weatherURL);
+   return WeatherLocation.fromJson(jsonDecode(res.body);
-   return compute(parseWeatherLocation, res.body);
  }

-  WeatherLocation parseWeatherLocation(String json){
-    return WeatherLocation.fromJson(jsonDecode(json));
-  }
}

also it's always useful to provide an actually runnable code (I'm referring to the unusable weatherURL) otherwise the issue will be hardly workable

@Mashood97 you bested me on the line 🥇

Mashood97 commented 4 years ago

@iapicca hahaha no problem brother when I was playing around with isolates and compute firstly that was the biggest mistake as a beginner I made. @dario-digregorio let me know if the compute now returns your API data.

dario-digregorio commented 4 years ago

@iapicca sadly not. I event put the methods in the main and left the class entirely as you can see. It still does not work. It is like it is not executing get( )

import 'dart:convert';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app/weather_model.dart';
import 'package:http/http.dart';

final String weatherURL = 'dfg';
Future<WeatherLocation> getWeatherLocation(Client client) async {
  Response res = await client.get(weatherURL);

  return compute(parseWeatherLocation, res.body);
}

WeatherLocation parseWeatherLocation(String json){
  return WeatherLocation.fromJson(jsonDecode(json));
}

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,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      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<MyHomePage> {
  Future<WeatherLocation> _weatherLocation;

  @override
  void initState() {
    super.initState();
    _weatherLocation = getWeatherLocation(Client());
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: FutureBuilder(
          future: _weatherLocation,
          builder:
              (BuildContext context, AsyncSnapshot snapshot) {
            if (snapshot.hasData) {
              print("hi");
              return Container();
            } else {
              return Center(child: CircularProgressIndicator());
            }
          },
        ));
  }
}
dario-digregorio commented 4 years ago

@dario-digregorio compute is sugar for isolate which need to take a top level or static function I'm not sure that this is the case for parseWeatherLocation

does the code below run?

import 'dart:convert';

import 'package:flutter/foundation.dart';
import 'package:flutter_app/weather_model.dart';
import 'package:http/http.dart';

class HttpService {
  final String weatherURL =
            'https://api.openweathermap.org/data/2.5/weather?lat=12.8578252&units=metric&lon=66.0999831&appid=<API_KEY>';

  Future<WeatherLocation> getWeatherLocation(Client client) async {
    Response res = await client.get(weatherURL);
+   return parseWeatherLocation(res.body);
-   return compute(parseWeatherLocation, res.body);
  }

  WeatherLocation parseWeatherLocation(String json){
    return WeatherLocation.fromJson(jsonDecode(json));
  }
}

also it's always useful to provide an actually runnable code (I'm referring to the unusable weatherURL) otherwise the issue will be hardly workable

@Mashood97 you bested me on the line 🥇

It does not run. You are passing the function call parseWeatherLocation as parameter of parseWeatherLocation(). Is there a typo?

iapicca commented 4 years ago

@dario-digregorio I've edited my previous post to make my point more clear, regardless it seems to me that http works just fine as for the code below and anyway if the problem was actually http package the issue should have been opened here

code sample ```dart import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; // dependencies: // flutter: // sdk: flutter // http: ^0.12.2 void main() => runApp(const MyApp(key: Key('app'))); class MyApp extends StatelessWidget { const MyApp({Key key}) : super(key: key); @override Widget build(BuildContext context) => const MaterialApp( home: MyHomePage(key: Key('home')), ); } class MyHomePage extends StatelessWidget { const MyHomePage({Key key}) : super(key: key); static const _url = 'https://swapi.dev/api/people/1/'; Widget _response(http.Response res) => res.statusCode != 200 ? Text('status code: ${res.statusCode}') : Text(jsonDecode(res.body)['name']); @override Widget build(BuildContext context) => Material( child: Center( child: FutureBuilder( future: http.get(_url), builder: (BuildContext context, AsyncSnapshot snapshot) => snapshot.hasError ? const Text('error') : !snapshot.hasData ? const CircularProgressIndicator() : _response(snapshot.data), ), ), ); } ```

if the issue persist w/o compute it very likely depends from the api or how the authentication is handled

dario-digregorio commented 4 years ago

I found the solution

Basically since the res.body would sometimes return null the Future will return a null and therefore at the check snapshot.hasData never build the other widget. By checking the statusCode of the response the method would only return a initialized value.

Future<WeatherLocation> getWeatherLocation(Client client) async {
  Response res = await client.get(weatherURL);
  if (res.statusCode == 200) {
      return compute(parseWeatherLocation, res.body);
    }
}

I will close the issue.

github-actions[bot] commented 3 years ago

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.