0

I am trying to make a simple POST request in flutter and trying to catch exception. I am seeing the exception in console but when I do print(snapshot.hasError) inside my FutureBuilder, it gives me false.

Since, I am new to flutter I don't know which part I am missing. Been stuck here for a while now.

Here is my post function in service.dart

Future <dynamic> postData(String url, dynamic data) async {

    try {
      final response = await post(_baseUrl + url, body: data);
      if (response.statusCode == 200) {
        return json.decode(response.body);
      } else {
        throw Exception('Failed to load data');
      }
    } catch (e) {
      throw Exception(e);
    }
  }

And my UI design class where I have put a condition to show the input text fields (Name and Email) first and once user fills up his input details I am showing FutureBuilder widget.

import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

import 'package:stationary/components/error_dialog.dart';
import 'package:stationary/services/base_helper.dart';
import 'package:stationary/services/signup_services.dart';
import 'package:stationary/size_config.dart';
import 'package:stationary/components/spacer.dart';

import '../../constants.dart' as Constants;

class InputUserDetails extends StatefulWidget {

  // final String userMobile;

  @override
  _InputUserDetailsState createState() => _InputUserDetailsState();
}

class _InputUserDetailsState extends State<InputUserDetails> {

  TextEditingController nameController = TextEditingController();
  TextEditingController emailController = TextEditingController();

  bool isButtonDisabled = true;
  bool showLoader = false;

  Future<dynamic> futureList;

  final storage = new FlutterSecureStorage();

  String _mobileNum;

  void checkButtonDisability(value) {
    var name = nameController.text;
    var email = emailController.text;
    if (name.length > 0 && email.length > 0) {
      setState(() {
        isButtonDisabled = false;
      });
    } else {
      setState(() {
        isButtonDisabled = true;
      });
    }
  }
  
  @override
  void initState() {
    super.initState();
    _getMobileNumber();
  }

  _getMobileNumber() async {
    var tempMobileNum = await storage.read(key: 'userMobile');
    setState(() {
      _mobileNum = tempMobileNum;
    });
  }

  signUpUser(name, email) async {
    String endpoint = Constants.postUserInfo;
    var json = {
      "email": email,
      "name": name,
      "phone_number": _mobileNum
    };
    print(json);
    futureList = await ApiBaseHelper().postData(endpoint, json);
  }

  @override
  Widget build(BuildContext context) {
    SizeConfig().init(context);
    return Scaffold(
      resizeToAvoidBottomInset: false,
      body: showLoader? FutureBuilder(
        future: futureList,
        builder: (context, snapshot) {
          // print(snapshot.hasData); // ================= false
          // print(snapshot.hasError); // ================ false

          if(snapshot.hasData) {
            print(snapshot.data);
            return Container();
          } else if(snapshot.hasError) {
            print(snapshot.error);
            return ErrorDialog('Some error occurred');
          }
          return CircularProgressIndicator();
        },
      ) : Container(
        margin: EdgeInsets.all(SizeConfig.safeBlockHorizontal * 5),
        child: SingleChildScrollView(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              SizedBox(
                height: SizeConfig.safeBlockVertical * 10,
              ),

              Container(
                child: Row(
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: [
                    Text(
                      'Enter your details',
                      style: TextStyle(
                        fontWeight: FontWeight.w500,
                        fontFamily: 'Roboto',
                        fontSize: SizeConfig.safeBlockHorizontal * 5,
                        color: Colors.white,
                      ),
                    ),

                    Container(
                      child: SpacerElement(),
                    ),
                  ],
                ),
              ),

              SizedBox(
                height: SizeConfig.safeBlockVertical * 2,
              ),

              SizedBox(height: SizeConfig.safeBlockVertical * 10, ),

              Flexible(
                child: TextFormField(
                  style: TextStyle(
                    fontWeight: FontWeight.w500,
                    fontFamily: 'Roboto',
                    fontSize: SizeConfig.safeBlockHorizontal * 4,
                    color: Colors.white,
                  ),
                  onChanged: (value) {
                    checkButtonDisability(value);
                  },
                  cursorColor: Colors.white,
                  controller: nameController,
                  keyboardType: TextInputType.text,
                  decoration: new InputDecoration(
                    hintText: 'Enter Your Name',
                    hintStyle: TextStyle(
                      color: Colors.white30,
                      fontFamily: 'Roboto',
                      fontWeight: FontWeight.w400,
                    ),
                    contentPadding: EdgeInsets.symmetric(vertical: 10, horizontal: 10),
                    isDense: true,
                    border: InputBorder.none,
                    focusedBorder: InputBorder.none,
                    enabledBorder: InputBorder.none,
                    errorBorder: InputBorder.none,
                    disabledBorder: InputBorder.none,
                  ),
                ),
              ),

              SizedBox(height: SizeConfig.safeBlockVertical * 5, ),

              Flexible(
                child: TextFormField(
                  style: TextStyle(
                    fontWeight: FontWeight.w500,
                    fontFamily: 'Roboto',
                    fontSize: SizeConfig.safeBlockHorizontal * 4,
                    color: Colors.white,
                  ),
                  onChanged: (value) {
                    checkButtonDisability(value);
                  },
                  cursorColor: Colors.white,
                  controller: emailController,
                  keyboardType: TextInputType.emailAddress,
                  decoration: new InputDecoration(
                    hintText: 'Enter Your Email',
                    hintStyle: TextStyle(
                      color: Colors.white30,
                      fontFamily: 'Roboto',
                      fontWeight: FontWeight.w400,
                    ),
                    contentPadding: EdgeInsets.symmetric(vertical: 10, horizontal: 10),
                    isDense: true,
                    border: InputBorder.none,
                    focusedBorder: InputBorder.none,
                    enabledBorder: InputBorder.none,
                    errorBorder: InputBorder.none,
                    disabledBorder: InputBorder.none,
                  ),
                ),
              ),

              SizedBox(height: SizeConfig.safeBlockVertical * 5, ),

              Container(
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(50),
                ),
                child: FlatButton(
                  padding: EdgeInsets.symmetric(
                    horizontal: SizeConfig.safeBlockHorizontal * 5,
                    vertical: SizeConfig.safeBlockVertical * 2
                  ),
                  color: isButtonDisabled ? Theme.of(context).primaryColor : Theme.of(context).buttonColor,
                  shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(5.0),
                  ),
                  onPressed: isButtonDisabled? () {} : () {
                    var email = emailController.text;
                    var name = nameController.text;
                    var flag = RegExp(r"^[a-zA-Z0-9.]+@[a-zA-Z0-9]+\.[a-zA-Z]+").hasMatch(email);
                    if(!flag) {
                      showDialog(context: context, child: ErrorDialog('Please enter a valid email'));
                    } else {
                      setState(() {
                        showLoader = true;
                      });
                      signUpUser(name, email);
                    }
                    // Navigator.push(context, MaterialPageRoute(builder: (context) => SearchMapsScreen()));
                  },
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Text(
                        'TAKE ME IN',
                        style: TextStyle(
                          fontWeight: FontWeight.w500,
                          fontFamily: 'Roboto',
                          color: isButtonDisabled ? Colors.white60 : Colors.black,
                          fontSize: SizeConfig.safeBlockHorizontal * 4,
                        ),
                      ),
                    ],
                  ),
                ),
              ),

            ],
          ),
        ),
      ),
    );
  }
}

Please suggest me where I am missing something and also if this is the right way of handling Future Builder widget and Column widget with input texts.

Any help would be appreciated.

Thanks in advance!

neerav94
  • 429
  • 1
  • 8
  • 15

2 Answers2

0

The problem is the logic on the onPressed

else {
  setState(() {
     showLoader = true;
  });
  signUpUser(name, email);
}

You change the value of showLoader before running the signUpUser method, so when the setState change the FutureBuilder the future futureList is null (wans't declared yet), also I think you could make that method sync because you're saying that futureList will hold a Future, so there is no reason to make it async/await

signUpUser(name, email) {
    String endpoint = Constants.postUserInfo;
    var json = {
      "email": email,
      "name": name,
      "phone_number": _mobileNum
    };
    print(json);
    futureList = ApiBaseHelper().postData(endpoint, json); //This is fine, both sides are of type Future<dynamic>
  }

and run it before the setState so the futureList is not referencing a null value before running the FutureBuilder

EdwynZN
  • 4,895
  • 2
  • 12
  • 15
  • Didn't work. I declared my futureList before and then did setState, still not able to catch the error. – neerav94 Jul 07 '20 at 06:34
  • Can I recommend changing `throw Exception(e)` to `rethrow` in the catch, and also delete the app, run `flutter clean build` to try again, I had a similar problem with dio package and FutureBuilder not catching the error even after changing my code – EdwynZN Jul 07 '20 at 14:55
0

try on catch statement return Future.error.

Ex:

catch (e){
    return Future.error(e.toString());
}
Eko Syahputra
  • 206
  • 2
  • 5