Иногда приложение Flutter вылетает после отправки данных
У меня проблема в моем приложении. Существует простая форма, в которой пользователь может отправлять текст и фотографии. Когда я отправляю данные, такие как текст и изображения, иногда мое приложение вылетает, оно показывает черный экран и зависает, но не всегда. Я не понимаю, в чем именно проблема. Вот мой исходный код:
class WriteComplainWidget extends StatefulWidget {
final GlobalKey<ScaffoldState> parentScaffoldKey;
List<Asset> images = List<Asset>();
RouteArgument routeArgument;
final VoidCallback onPressed;
Brand brand;
WriteComplainWidget(
{Key key,
this.parentScaffoldKey,
this.routeArgument,
this.onPressed,
this.brand})
: super(key: key);
Complaint complaint = new Complaint();
@override
_WriteComplainWidgetState createState() => _WriteComplainWidgetState();
}
class _WriteComplainWidgetState extends StateMVC<WriteComplainWidget> {
GlobalKey<FormState> _complaintFormKey = new GlobalKey<FormState>();
GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
List<Asset> images = List<Asset>();
File _video;
String _error = 'No Error Dectected';
bool isSubmitted = false;
int counter = 0;
VideoPlayerController _videoPlayerController;
VideoPlayerController _cameraVideoPlayerController;
TextEditingController brandNameController = new TextEditingController();
@override
void initState() {
super.initState();
ErrorWidget.builder = (FlutterErrorDetails details) => Container(
decoration: BoxDecoration(color: Colors.white),
alignment: Alignment.center,
child: Center(
child: Padding(
padding: const EdgeInsets.all(30.0),
child: Container(
height: 300,
width: 300,
child: Center(
child: Text(
S.current.an_error_has_occurred_please_restart_application,
style: Theme.of(context).textTheme.title,
),
),
),
),
),
);
}
@override
Widget build(BuildContext context) {
return isSubmitted == true
? CircularLoadingWidget(height: 500)
: Scaffold(
appBar: AppBar(
leading: new IconButton(
icon: new Icon(Icons.sort, color: Theme.of(context).hintColor),
onPressed: () =>
widget.parentScaffoldKey.currentState.openDrawer(),
),
automaticallyImplyLeading: false,
backgroundColor: Colors.transparent,
elevation: 0,
centerTitle: true,
title: Text(
S.of(context).write_complaint,
style: Theme.of(context)
.textTheme
.title
.merge(TextStyle(letterSpacing: 0.1)),
),
actions: <Widget>[
Stack(
children: <Widget>[
IconButton(
iconSize: 25,
icon: Icon(
Icons.notifications,
color: Color(0xFF5ea1c8),
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NotificationsWidget()),
);
setState(() {
counter = 0;
});
}),
counter != 0
? new Positioned(
right: 11,
top: 11,
child: new Container(
padding: EdgeInsets.all(2),
decoration: new BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(6),
),
constraints: BoxConstraints(
minWidth: 14,
minHeight: 14,
),
child: Text(
'$counter',
style: TextStyle(
color: Colors.white,
fontSize: 8,
),
textAlign: TextAlign.center,
),
),
)
: new Container()
],
),
],
),
body: SingleChildScrollView(
child: Center(
child: Form(
key: _complaintFormKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Padding(
padding:
const EdgeInsets.only(top: 20, left: 35, right: 35),
child: Container(
child: TypeAheadFormField(
onSaved: (input) =>
widget.complaint.brandName = input,
validator: (input) => input.length < 1
? S.of(context).please_enter_brand_name
: null,
noItemsFoundBuilder: (BuildContext context) => Text(
S.current.not_found_no_such_brand,
style: TextStyle(
color: Theme.of(context).errorColor,
fontSize: 18)),
textFieldConfiguration: TextFieldConfiguration(
maxLength: 512,
style: new TextStyle(color: Colors.black),
controller: brandNameController,
autofocus: false,
decoration: InputDecoration(
filled: true,
fillColor: Colors.white,
labelText: S.of(context).brand_name,
labelStyle: TextStyle(
color: Theme.of(context).focusColor),
contentPadding: EdgeInsets.all(12),
hintStyle: TextStyle(color: Colors.black),
prefixIcon: Icon(Icons.business,
color: Theme.of(context).accentColor),
border: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context)
.focusColor
.withOpacity(0.2))),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context)
.focusColor
.withOpacity(0.5))),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context)
.focusColor
.withOpacity(0.3))),
),
),
suggestionsCallback: (pattern) async {
return BackendService.searchBrands(pattern);
},
itemBuilder: (context, suggestion) {
return ListTile(
title: Text(suggestion['name']),
);
},
onSuggestionSelected: (suggestion) {
widget.complaint.brandId =
suggestion["id"].toString();
brandNameController.text =
suggestion["name"].toString();
},
),
),
),
Padding(
padding: const EdgeInsets.only(left: 35, right: 35),
child: Container(
child: TextFormField(
onSaved: (input) => widget.complaint.body = input,
validator: (input) => input.length < 50
? S.of(context).please_describe_in_details
: null,
keyboardType: TextInputType.text,
maxLines: 5,
maxLength: 2500,
autofocus: false,
style: new TextStyle(color: Colors.black),
decoration: InputDecoration(
filled: true,
fillColor: Colors.white,
labelText: S.of(context).your_complaint,
labelStyle: TextStyle(
color: Theme.of(context).focusColor),
contentPadding: EdgeInsets.all(12),
hintStyle: TextStyle(
color: Theme.of(context)
.focusColor
.withOpacity(0.7)),
prefixIcon: Icon(Icons.insert_drive_file,
color: Theme.of(context).accentColor),
border: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context)
.focusColor
.withOpacity(0.2))),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context)
.focusColor
.withOpacity(0.5))),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context)
.focusColor
.withOpacity(0.3))),
),
),
),
),
Padding(
padding: const EdgeInsets.only(left: 30.0, top: 10),
child: Wrap(
children: <Widget>[
SizedBox(
height: 5,
),
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Column(
children: <Widget>[
ComplaintButton(
color: Theme.of(context).accentColor,
onPressed: loadAssets,
icon: Icons.add_a_photo)
],
),
Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
ComplaintButton(
color: Theme.of(context).accentColor,
icon: Icons.videocam,
onPressed: () {
_pickVideo();
},
),
if (_video != null)
_videoPlayerController.value.initialized
? Expanded(
child: Stack(
children: <Widget>[
Container(
height: 100,
width: 100,
child: AspectRatio(
aspectRatio:
_videoPlayerController
.value
.aspectRatio,
child: VideoPlayer(
_videoPlayerController),
),
),
Positioned(
top: -15,
right: -15,
child: ClipRRect(
borderRadius:
BorderRadius.circular(
16.0),
child: IconButton(
hoverColor: Colors.red,
icon: Icon(
Icons.delete,
color: Colors.red,
size: 23,
),
),
),
),
],
),
)
: Container()
else
Text(
"Clic",
style: TextStyle(fontSize: 18.0),
),
],
),
SizedBox(width: 70),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
buildGridView(),
],
),
),
],
),
],
),
),
Padding(
padding: const EdgeInsets.only(
left: 80.0, right: 80, bottom: 30, top: 30),
child: BlockButtonWidget(
text: Text(
S.of(context).submit,
style: TextStyle(
color: Theme.of(context).primaryColor),
),
color: Theme.of(context).accentColor,
onPressed: _submit,
),
),
],
),
),
),
),
);
}
void _removePicture(int index) {
images.removeAt(index);
setState(() {
images = images;
});
}
Widget buildGridView() {
return images.length > 0
? Container(
height: 50,
width: 200,
child: GridView.count(
mainAxisSpacing: 7.0,
crossAxisSpacing: 4.0,
crossAxisCount: 4,
children: List.generate(images.length, (index) {
Asset asset = images[index];
return Stack(
children: <Widget>[
AssetThumb(asset: asset, width: 150, height: 150),
Positioned(
top: -15,
right: -15,
child: ClipRRect(
borderRadius: BorderRadius.circular(16.0),
child: IconButton(
hoverColor: Colors.red,
icon: Icon(
Icons.delete,
color: Colors.red,
size: 23,
),
onPressed: () => _removePicture(index),
),
),
),
],
);
}),
),
)
: Row();
}
Future<void> loadAssets() async {
FocusScope.of(context).requestFocus(new FocusNode());
List<Asset> resultList = List<Asset>();
String error = 'No Error Dectected';
try {
resultList = await MultiImagePicker.pickImages(
maxImages: 4,
enableCamera: true,
selectedAssets: images,
cupertinoOptions: CupertinoOptions(takePhotoIcon: "chat"),
materialOptions: MaterialOptions(
actionBarColor: "#abcdef",
actionBarTitle: "Example App",
allViewTitle: "All Photos",
useDetailsView: false,
selectCircleStrokeColor: "#000000",
),
);
} on Exception catch (e) {
error = e.toString();
}
if (!mounted) return;
setState(() {
images = resultList;
_error = error;
});
}
void _submit() async {
if (_complaintFormKey.currentState.validate()) {
_complaintFormKey.currentState.save();
// string to uri
Uri uri = Uri.parse('https://api.panaszoktest.com/v1/complaint/submit');
developer.log(uri.toString() + DateTime.now().toString(),
name: "uri-picture");
// create multipart request
http.MultipartRequest request = http.MultipartRequest("POST", uri);
//
setState(() {
isSubmitted = true;
});
if (images.length > 0) {
Flushbar(
isDismissible: true,
flushbarPosition: FlushbarPosition.TOP,
message: S.of(context).please_wait,
margin: EdgeInsets.all(8),
borderRadius: 8,
icon: Icon(
Icons.info_outline,
size: 28.0,
color: Colors.blue[300],
),
duration: Duration(seconds: 5),
leftBarIndicatorColor: Colors.green[300],
)..show(context);
for (var i = 0; i < images.length; i++) {
ByteData byteData = await images[i].getByteData();
developer.log(images[i].name.toString() + DateTime.now().toString(),
name: "uri-picture");
List<int> imageData = byteData.buffer.asUint8List();
developer.log(
images[i].originalHeight.toString() + DateTime.now().toString(),
name: "uri-picture");
http.MultipartFile multipartFile = http.MultipartFile.fromBytes(
'photo' + i.toString(),
imageData,
filename: images[i].name,
contentType: MediaType("image", "jpg"),
);
developer.log(images[i].name.toString() + DateTime.now().toString(),
name: "uri-picture");
// add file to multipart
request.files.add(multipartFile);
developer
.log(request.files.add.toString() + DateTime.now().toString());
}
} else {}
User _user = await getCurrentUser();
request.fields["api_token"] = _user.apiToken;
request.fields["subject"] = widget.complaint.subject;
request.fields["body"] = widget.complaint.body;
request.fields["brandName"] = widget.complaint.brandName;
request.fields["brandId"] =
widget.complaint.brandId != null ? widget.complaint.brandId : "";
//request.fields["brandBranchName"] = widget.complaint.brandBranchName;
request.fields["userId"] = _user.id.toString();
request.fields["lang"] = "en";
// send
//var streamedResponse = await request.send();
request.send().then((res) async {
var response = await http.Response.fromStream(res);
Map<String, dynamic> data = json.decode(response.body);
if (data["data"] != null &&
data["data"]["success"] != null &&
data["data"]["success"] == true) {
isSubmitted = false;
Navigator.popAndPushNamed(context, "/Pages");
Flushbar(
isDismissible: true,
flushbarPosition: FlushbarPosition.TOP,
message: S.of(context).your_complaint_has_been_submitted,
margin: EdgeInsets.all(8),
borderRadius: 8,
icon: Icon(
Icons.info_outline,
size: 28.0,
color: Colors.blue[300],
),
duration: Duration(seconds: 3),
leftBarIndicatorColor: Colors.green[300],
)..show(context);
} else if (data["data"] != null &&
data["data"]["errorMessage"] != null) {
setState(() {
isSubmitted = false;
});
developer.log(data["data"]["errorMessage"],
name: "response-image-upload2");
Flushbar(
isDismissible: true,
flushbarPosition: FlushbarPosition.TOP,
message: S.of(context).failed,
margin: EdgeInsets.all(8),
borderRadius: 8,
icon: Icon(
Icons.info_outline,
size: 28.0,
color: Colors.blue[300],
),
duration: Duration(seconds: 5),
leftBarIndicatorColor: Colors.red[300],
)..show(context);
}
}).catchError((err) {
print(err);
});
}
}
}
----------------------------- здесь backend servcie --------------------------------------------------
class BackendService {
static Future<List> searchBrands(
String search,
) async {
List brands = new List();
User _user = await getCurrentUser();
final String _apiToken = 'api_token=${_user.apiToken}';
final String url =
'${GlobalConfiguration().getString('api_base_url')}brand/search?$_apiToken&query=${search}';
developer.log(url, name: "request-brand-search-url");
final client = new http.Client();
final streamedRest = await client.send(http.Request('get', Uri.parse(url)));
var response = await http.Response.fromStream(streamedRest);
developer.log(response.body, name: "brand search results");
Map<String, dynamic> data = json.decode(response.body);
for (var i = 0; i < data["data"].length; i++) {
brands.add({
"id": data["data"][i]["id"],
"name": data["data"][i]["name"],
"logoUrl": data["data"][i]["logoImage"][0]["url"]
});
}
return brands;
}
}