This question already has answers here:
What is a Future and how do I use it?
(6 answers)
Closed 2 years ago.
I am trying to parse an api in flutter. the asynchronous request delays the response which is shown by the progress indicator. but the progress indicator does not fade away after the response is fetched. it remains still on the screen.
how to change the state of the progress loader to listview.builder when the response is fetched?
this is my code.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'users.dart';
import 'package:http/http.dart' as http;
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
bool isLoading = false;
List<Users> _users;
Future<List<Users>> getUsers() async {
var response = await http.get(Uri.encodeFull('http://jsonplaceholder.typicode.com/users'));
_users = usersFromJson(response.body);
isLoading = true;
}
#override
void initState() {
setState(() {
getUsers();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(isLoading ? "Parsed Json" : "Loading..."),
backgroundColor: Colors.deepPurple,
),
body: isLoading ? ListView.builder(
itemCount: _users.length,
itemBuilder: (BuildContext context,int index){
return ListTile(
leading: Image.network('https://images.unsplash.com/photo-1488426862026-3ee34a7d66df?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=334&q=80'),
title: Text(_users[index].name),
);
}) : Center(child: CircularProgressIndicator()),
);
}
}
Working code, try this
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
bool isLoading = false;
List<Users> _userList = [];
Future<void> getUsers() async {
isLoading = true;
var response = await http
.get(Uri.encodeFull('http://jsonplaceholder.typicode.com/users'));
var jsonResponse = jsonDecode(response.body);
// print(jsonResponse);
jsonResponse.forEach((data) {
Users user = Users.fromMap(data);
print(user);
_userList.add(user);
});
isLoading = false;
}
#override
void initState() {
getUsers().then((value) => setState(() {}));
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(isLoading ? "Loading..." : "Parsed Json"),
backgroundColor: Colors.deepPurple,
),
body: isLoading
? Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: _userList.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(_userList[index].name),
);
}));
}
}
class Users {
int id;
String name;
String username;
Users({
this.id,
this.name,
this.username,
});
Map<String, dynamic> toMap() {
return {
'id': id,
'name': name,
'username': username,
};
}
factory Users.fromMap(Map<String, dynamic> map) {
if (map == null) return null;
return Users(
id: map['id'],
name: map['name'],
username: map['username'],
);
}
String toJson() => json.encode(toMap());
factory Users.fromJson(String source) => Users.fromMap(json.decode(source));
#override
String toString() => 'Users(id: $id, name: $name, username: $username)';
}
Check your _user != null instead of bool then show list otherwise indicator
void initState () method Called when this object is inserted into the tree. The framework will call this method exactly once for each State object it creates. so there is no need for setState in initState() method.
try set setState after the response, so
#override
void initState() {
super.initState();
getUsers();
}
Future<List<Users>> getUsers() async {
isLoading = false;
var response = await http.get(Uri.encodeFull('http://jsonplaceholder.typicode.com/users'));
_users = usersFromJson(response.body);
print(_users);
setState(() {
isLoading = true;
});
return _users;
}
Related
I am using an API call to get some data. And I am calling that method in the init state so that it fetches the data as soon as the widget is added into the tree.
I am using that data in a Text widget, but it shows an error that "The method '[]' was called on null".
Here is the code for your reference:
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class Profile extends StatefulWidget {
final String username;
Profile({this.username});
#override
_ProfileState createState() => _ProfileState();
}
class _ProfileState extends State<Profile> {
var data;
void getData() async {
http.Response response = await http
.get('http://codeforces.com/api/user.info?handles=${widget.username}');
if (response.statusCode == 200) {
data = jsonDecode(response.body);
} else {
print('Something went wrong.');
}
}
#override
void initState() {
super.initState();
getData();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Column(
children: [
Text('Handle - ${widget.username}'),
Text(data['result'][0]['firstName']), /*This line causes error*/
],
),
),
);
}
}
The question is why the data field is null even though it is being called in the init state and the API call is also successful?
I have marked the line which gives the error using a comment.
I think it could be happen because in the time that the request are being done, your Profile widget was already rendered, and you're not using setState in your getData() function, so, the widget will not render again.
In this case, you can do some changes in your code:
Use a setState in your getData method
void getData() async {
http.Response response = await http
.get('http://codeforces.com/api/user.info?handles=${widget.username}');
if (response.statusCode == 200) {
setState(() {
data = jsonDecode(response.body);
});
} else {
print('Something went wrong.');
}
}
Set a default value to your Text widget
Text(data != null ? data['result'][0]['firstName'] : ''),
not the best and efficient solution but if you just want to show some data like username or email you can do it like this:
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
void main() => runApp(Profile());
class Profile extends StatefulWidget {
final String username;
Profile({this.username});
#override
_ProfileState createState() => _ProfileState();
}
class _ProfileState extends State<Profile> {
var email;
void getData() async {
http.Response response = await http.get(Uri.parse(
'http://codeforces.com/api/user.info?handles=${widget.username}'));
if (response.statusCode == 200) {
setState(() {
Map<String, dynamic> map = json.decode(response.body);
email = map['result'][0]['email'];
print(email);
});
} else {
print('Something went wrong.');
}
}
#override
void initState() {
super.initState();
getData();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: email == null
? CircularProgressIndicator()
: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('Handle - ${widget.username}'),
Text(email),
],
),
),
),
);
}
}
output:
My Result
Source Code
Loading Screen
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:clima/services/location.dart';
import 'package:http/http.dart'as http;
class LoadingScreen extends StatefulWidget {
#override
_LoadingScreenState createState() => _LoadingScreenState();
}
class _LoadingScreenState extends State<LoadingScreen> {
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
void getlocation()async{
Location location=Location();
await location.getcurrentlocation();
print(location.lat);
print(location.long);
}
void getdata()async{
http.Response response = await http.get(Uri.parse
('samples.openweathermap.org/data/2.5/forecast?q=London,us&mode=xml&appid=b6907d289e10d714a6e88b30761fae22')
);
if (response.statusCode == 200) {
String data = response.body;
print(data);
} else {
print(response.statusCode);
}}
Widget build(BuildContext context) {
getlocation();
getdata();
return Scaffold(
);
}
}
Get CurrentLocation Method
import 'package:geolocator/geolocator.dart';
class Location{
double long;
double lat;
Future<void> getcurrentlocation() async {
try {
Position position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.low);
lat = position.latitude;
long= position.longitude;
} catch (e) {
}
}
}
I want this result(https://samples.openweathermap.org/data/2.5/weather?lat=35&lon=139&appid=b6907d289e10d714a6e88b30761fae22) but unable to get this. Can you please tell me where I am wrong
You have to add the https:// to your url.
like this:
Uri.parse('https://samples.openweathermap.org/data/2.5/weather?lat=35&lon=139&appid=b6907d289e10d714a6e88b30761fae22');
Also, please always post your code in text, not images.
Here is another approach
var url = Uri.https('samples.openweathermap.org', 'data/2.5/weather', {
'lat': '35',
'lon': '139',
'appid': 'b6907d289e10d714a6e88b30761fae22'
});
http.Response weatherResponse = await http.get(url);
I have attached the code. How can I fetch the PDF from assets? I think calling the init function could solve, but I couldn't call it perfect. I want to fetch the pdf without using any button or else something.
So, I need to fetch it directly as soon as the Class InformationTechnology call.
class InformationTechnology extends StatefulWidget {
#override
_InformationTechnologyState createState() => _InformationTechnologyState();
}
class InformationTechnology extends StatefulWidget {
#override
_InformationTechnologyState createState() => _InformationTechnologyState();
}
class _InformationTechnologyState extends State<InformationTechnology> {
String pdfasset = "assets/IT.pdf";
PDFDocument _doc;
bool _loading = false;
#override
void initState() {
super.initState();
}
_initPDF()async{
setState(() {
_loading = true;
});
final doc = await PDFDocument.fromAsset(pdfasset);
setState(() {
_doc = doc;
_loading = false;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Introduction to Information Technology"),
centerTitle: true,
backgroundColor: Colors.blueAccent,
),
body: _loading?
Center(
child: CircularProgressIndicator(),
):PDFViewer(document: _doc),
);
}
}
I want my Login Screen to be shown if a user isn't logged in and the MainPage to be shown if the user is logged in from the device. For GitHub authentication, the browser is being opened and once the authentication is successful, I want the user to be redirected to the MainPage and not the login screen, but with my code, the user is redirected again to the Login Screen. The code for the login_screen.dart is,
import 'package:flutter/material.dart';
import 'package:githubapp/custom_widgets/custom_login_page.dart';
import 'package:githubapp/screens/user_initial_screen.dart';
import 'package:url_launcher/url_launcher.dart';
import 'dart:async';
import 'package:uni_links/uni_links.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
class LoginPage extends StatefulWidget {
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
StreamSubscription _subs;
#override
void initState() {
_initDeepLinkListener();
super.initState();
}
#override
void dispose() {
_disposeDeepLinkListener();
super.dispose();
}
void _initDeepLinkListener() async {
_subs = getLinksStream().listen((String link) {
_checkDeepLink(link);
}, cancelOnError: true);
}
void _checkDeepLink(String link) {
if (link != null) {
String code = link.substring(link.indexOf(RegExp('code=')) + 5);
loginWithGitHub(code)
.then((firebaseUser) {
print("LOGGED IN AS: " + firebaseUser.displayName);
Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (BuildContext context){
return MainPage();
}));
}).catchError((e) {
print("LOGIN ERROR: " + e.toString());
});
}
}
void _disposeDeepLinkListener() {
if (_subs != null) {
_subs.cancel();
_subs = null;
}
}
#override
Widget build(BuildContext context) {
return customLoginPage(context);
}
}
const String GITHUB_CLIENT_ID = "My Client Id";
const String GITHUB_CLIENT_SECRET = "My Client Secret";
void onClickGitHubLoginButton() async {
const String url = "https://github.com/login/oauth/authorize" +
"?client_id=" + GITHUB_CLIENT_ID +
"&scope=public_repo%20read:user%20user:email";
if (await canLaunch(url)) {
await launch(
url,
forceSafariVC: false,
forceWebView: false,
);
} else {
print("CANNOT LAUNCH THIS URL!");
}
}
Future<FirebaseUser> loginWithGitHub(String code) async {
//ACCESS TOKEN REQUEST
final response = await http.post(
"https://github.com/login/oauth/access_token",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
body: jsonEncode(GitHubLoginRequest(
clientId: GITHUB_CLIENT_ID,
clientSecret: GITHUB_CLIENT_SECRET,
code: code,
)),
);
GitHubLoginResponse loginResponse =
GitHubLoginResponse.fromJson(json.decode(response.body));
final AuthCredential credential = GithubAuthProvider.getCredential(
token: loginResponse.accessToken,
);
final FirebaseUser user = (await FirebaseAuth.instance.signInWithCredential(credential)).user;
return user;
}
class GitHubLoginRequest {
String clientId;
String clientSecret;
String code;
GitHubLoginRequest({this.clientId, this.clientSecret, this.code});
dynamic toJson() => {
"client_id": clientId,
"client_secret": clientSecret,
"code": code,
};
}
class GitHubLoginResponse {
String accessToken;
String tokenType;
String scope;
GitHubLoginResponse({this.accessToken, this.tokenType, this.scope});
factory GitHubLoginResponse.fromJson(Map<String, dynamic> json) =>
GitHubLoginResponse(
accessToken: json["access_token"],
tokenType: json["token_type"],
scope: json["scope"],
);
}
The code on the main.dart is,
import 'package:flutter/material.dart';
import 'package:githubapp/auth/login_screen.dart';
import 'package:githubapp/screens/user_initial_screen.dart';
import 'package:githubapp/screens/user_profile_page.dart';
import 'package:firebase_auth/firebase_auth.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
// This widget is the root of your application.
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String routeToMainPage = 'mainPage';
String routeToLoginPage = 'loginPage';
String routeInitial = '';
getCurrentUser() async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
return user;
}
#override
void initState() {
// TODO: implement initState
super.initState();
if(getCurrentUser() != null){
routeInitial = routeToMainPage;
} else {
routeInitial = routeToLoginPage;
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'GitHub',
initialRoute: '/${routeInitial}',
routes: {
'/mainPage': (context) => MainPage(),
'/loginPage': (context) => LoginPage(),
'/userprofile': (context) => UserProfilePage(),
},
theme: ThemeData(
primarySwatch: Colors.blue,
),
);
}
}
The code where the MainPage() function is present is,
import 'package:flutter/material.dart';
import 'package:githubapp/custom_widgets/custom_appbar.dart';
class MainPage extends StatefulWidget {
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
#override
Widget build(BuildContext context) {
return GestureDetector(
child: Scaffold(
appBar: customAppBar(context),
// body: ,
),
onTap: () {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
},
);
}
}
For the Authorization callback URL, which we have to enter while getting the ClientId and ClientSecret from GitHub is,
appname://auth?code=auth/
you should navigate user to mainPage after login was successfully
void _checkDeepLink(String link) {
if (link != null) {
String code = link.substring(link.indexOf(RegExp('code=')) + 5);
loginWithGitHub(code)
.then((firebaseUser) {
print("LOGGED IN AS: " + firebaseUser.displayName);
Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (BuildContext context){
return MainPage();
}));
}).catchError((e) {
print("LOGIN ERROR: " + e.toString());
});
}
}
I am recreating an app I have previously made in Swift, and on one of my pages we call an API and based on the results, we present the user a dynamic number of textfields to search by different search parameters.
Is there any good way to do this in Dart/Flutter? Since dart doesn't support generating code at runtime, is this even a possibility?
I just modified #Felix's answer with using Map to store TextEditingControllers instead of list. I think its easy to call textEditingControllers with key value pairs.
Modified code block;
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "MyHomePage",
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(
title: "MyHomePage",
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
var stringListReturnedFromApiCall = ["first", "second", "third", "fourth", "..."];
// This list of controllers can be used to set and get the text from/to the TextFields
Map<String,TextEditingController> textEditingControllers = {};
var textFields = <TextField>[];
stringListReturnedFromApiCall.forEach((str) {
var textEditingController = new TextEditingController(text: str);
textEditingControllers.putIfAbsent(str, ()=>textEditingController);
return textFields.add( TextField(controller: textEditingController));
});
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SingleChildScrollView(
child: new Column(
children:[
Column(children: textFields),
RaisedButton(
child: Text("Print Values"),
onPressed: (){
stringListReturnedFromApiCall.forEach((str){
print(textEditingControllers[str].text);
});
})
]
)));
}
}
when you write something to textfields and hit to print button results ;
flutter: first controller text
flutter: second controller text
flutter: third controller text
flutter: fourth controller text
flutter: so on .......
More or less as #Günter Zöchbauer mentioned, you can just build a list of widgets which are nested then in a container.
Here's a simple example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "MyHomePage",
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(
title: "MyHomePage",
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
var stringListReturnedFromApiCall = ["first", "second", "third", "fourth", "..."];
// This list of controllers can be used to set and get the text from/to the TextFields
var textEditingControllers = <TextEditingController>[];
var textFields = <TextField>[];
stringListReturnedFromApiCall.forEach((str) {
var textEditingController = new TextEditingController(text: str);
textEditingControllers.add(textEditingController);
return textFields.add(new TextField(controller: textEditingController));
});
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SingleChildScrollView(
child: new Column(
children: textFields,
)));
}
}
Edit: Added list for TextEditingControllers to interact with all the TextFields
Using list of textEditingControllers instead of map
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var stringListReturnedFromApiCall = ["first", "second", "third", "fourth", "..."];
List<TextEditingController> textEditingControllers = [];
#override
void initState() {
super.initState();
stringListReturnedFromApiCall.forEach((String str) {
var textEditingController = TextEditingController(text: str);
textEditingControllers.add(textEditingController);
});
}
#override
void dispose() {
super.dispose();
// dispose textEditingControllers to prevent memory leaks
for (TextEditingController textEditingController in textEditingControllers) {
textEditingController?.dispose();
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('title'),
),
body: ListView.builder(
itemCount: stringListReturnedFromApiCall.length,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: TextField(
controller: textEditingControllers[index],
),
);
},
),
);
}
}
list that contains your controllers
var myControllers = [];
function called in order to populate myControllers
createControllers() {
myControllers = [];
for (var i = 0; i < your_items.length; i++) {
myControllers.add(TextEditingController());
}
}
init createKeys in your statefullwidget
use it like this, for example in listview builder ;
TextField(
controller: myControllers[index],
),
May be this can help you or other's as well.
createFieldsList(context) {
thirdStepUserRegistration.value.forEach((key, value) { //// This line will loop all your data [ValueNotifier<Map<String, dynamic>> thirdStepUserRegistration]
if (!fieldsController.containsKey(key)) {
fieldsController[key] = TextEditingController(); //// This one will create you a list of controllers that u need for your fiels [Map<String, TextEditingController> fieldsController]
fieldsList.add( ////You will be creating a list of widget which is textfields [List<Widget> fieldsList]
Container(
height: 40.0,
margin: EdgeInsets.only(bottom: 10),
width: MediaQuery.of(context).size.width - 100,
child: PrimaryTextFormField( //This is my customize textfield you can create yours
controller: fieldsController[key],
keyboardType: TextInputType.text,
textCapitalization: TextCapitalization.words,
inputFormatters: textFormatter(),
hintText: value,
),
)
);
}
});
}
Column additionalDetails(BuildContext context) {
createFieldsList(context);
return Column( children: fieldsList );
}