Hi I’m really new in flutter, its been 5 days for me learning flutter. Right now I’m struggling with map list of widget. I’m not sure what its called, but in JS (react, react native) array.map()
. So my Scenario is that I have custom widget, then I receive response from api, I would like to map that response to List/Array of Widget not sure what its called.
I receive error in this part in final List<Map<String, dynamic>> candidates = snapshot.data['data']['positive'];
, the error is The method '[]' can't be unconditionally invoked because the receiver can be 'null'. Try making the call conditional (using '?.') or adding a null check to the target ('!').
. I
I already have tried add ?
& !
like this final List<Map<String, dynamic>> candidates = snapshot.data!['data']!['positive'] as List<Map<String, dynamic>>;
but still error
Please help, I have already tried chatgpt too, but still no solution.
*Note: I’m using getx pattern
here my customWidget:
import 'package:flutter/material.dart';
import 'package:readmore/readmore.dart';
class VoteWidget extends StatelessWidget {
final String imgUrl;
final String candidateName;
final String candidateDesc;
final List candidateReasons;
final String selectedRadioButton;
final Function(dynamic) onChanged;
const VoteWidget(
{super.key,
required this.imgUrl,
required this.candidateName,
required this.candidateDesc,
required this.candidateReasons,
required this.selectedRadioButton,
required this.onChanged});
@override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
child: Container(
color: Colors.white,
margin: const EdgeInsets.symmetric(horizontal: 2),
child: Card(
color: Colors.white,
shadowColor: Colors.grey.shade400,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
child: ListView(
children: [
ClipRRect(
borderRadius: const BorderRadius.only(
topRight: Radius.circular(10),
topLeft: Radius.circular(10)),
child: Image.network(
imgUrl,
fit: BoxFit.cover,
height: 350,
),
),
Padding(
padding: const EdgeInsets.all(12.0),
child: Text(
candidateName,
style: const TextStyle(
fontSize: 18,
color: Colors.black,
fontWeight: FontWeight.bold),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: ReadMoreText(
candidateDesc,
trimLines: 3,
style: const TextStyle(fontSize: 12.0),
trimMode: TrimMode.Line,
trimCollapsedText: ' show more',
trimExpandedText: ' show less',
moreStyle: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.red.shade900),
lessStyle: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.red.shade900),
),
),
const Padding(
padding: EdgeInsets.all(12.0),
child: Text(
'Alasan :',
style: TextStyle(
color: Colors.black,
fontSize: 12.0,
fontWeight: FontWeight.bold),
),
),
...candidateReasons.map((item) => Container(
margin: const EdgeInsets.symmetric(
horizontal: 8, vertical: 2),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: BorderSide(
color: Colors.grey.withOpacity(0.2), width: 1)),
child: Row(
children: [
Container(
margin: const EdgeInsets.only(left: 16),
child: Text(
item['reason_description'],
style: const TextStyle(fontSize: 12.0),
)),
const Spacer(),
Transform.scale(
scale: 0.8,
child: Radio(
value: item['reason_id'],
groupValue: selectedRadioButton,
onChanged: onChanged))
],
),
),
))
],
),
),
))
],
);
}
}
here is where I want to use my custom widget, in VoteView()
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:invote/components/vote.dart';
import '../controllers/vote_controller.dart';
import 'package:carousel_slider/carousel_slider.dart';
class VoteView extends GetView<VoteController> {
const VoteView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final voteController = Get.put(VoteController());
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
automaticallyImplyLeading: false,
toolbarHeight: kToolbarHeight * 1.2,
title: Container(
margin: const EdgeInsets.only(top: 18),
child: const Text(
'My Vote',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.black),
),
),
),
body: Container(
margin: const EdgeInsets.only(top: 16, bottom: 16),
child: FutureBuilder(
future: voteController.getCandidates(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
} else if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
final List<Map<String, dynamic>> candidates = snapshot.data['data']['positive'];
return CarouselSlider(
options: CarouselOptions(
height: MediaQuery.of(context).size.height,
viewportFraction: 0.9,
enableInfiniteScroll: false,
onPageChanged: (index, reason) {
voteController.currentIndex = index;
},
scrollDirection: Axis.horizontal,
),
items: candidates.map((candidate) {
return VoteWidget(
imgUrl: candidate['candidate_picture'],
candidateName: candidate['candidates_name'],
candidateDesc: candidate['candidate_description'],
candidateReasons: candidate['reasons'],
selectedRadioButton: voteController.selected.value,
onChanged: (value) {
voteController.selected.value = value.toString();
},
);
}).toList(),
);
}
})));
}
}
here is my controller, just focus on getCandidates()
import 'dart:convert';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:invote/api/api_config.dart';
import 'package:invote/app/modules/profile/profile_model.dart';
// import 'package:invote/app/modules/vote/candidates_model.dart';
import 'package:invote/main.dart';
class VoteController extends GetxController {
//TODO: Implement VoteController
final count = 0.obs;
int currentIndex = 0;
var loading = false.obs;
RxString selected = ''.obs;
// RxList<Candidates> listPostive = <Candidates>[].obs;
// RxList<Candidates> listNegative = <Candidates>[].obs;
var profile = Profile().obs;
var bestCandidate = Positive().obs;
var worstCandidate = Positive().obs;
@override
void onInit() {
super.onInit();
getUser();
if (profile.value.bestCandidateId == null) {
getCandidates();
}
}
@override
void onReady() {
super.onReady();
}
@override
void onClose() {
super.onClose();
}
void increment() => count.value++;
getCandidates() async {
try {
var urlPositive = Uri.https(baseUrl, '/v1/reasons');
var urlNegative = Uri.https(baseUrl, '/v1/reasons', {'isWorst': 'true'});
var token = await box.read("token");
var responsePositive =
await http.get(urlPositive, headers: {"token": token});
var responseNegative =
await http.get(urlNegative, headers: {"token": token});
// // print(jsonDecode(responsePositive.body));
// // print(jsonDecode(responseNegative.body));
// listPostive.value = jsonDecode(responsePositive.body)['data'];
// listNegative.value = jsonDecode(responseNegative.body)['data'];
return {
"status": responsePositive.statusCode,
"data": {
"positive": jsonDecode(responsePositive.body)['data'],
"negative": jsonDecode(responseNegative.body)['data']
},
};
} catch (e) {
print("Response error get candidates: $e");
return {"status": 500, "body": "Internal Server Error"};
} finally {}
}
getUser() async {
try {
var url = Uri.https(baseUrl, '/v1/users');
var voteUrl = Uri.https(baseUrl, '/v1/vote/result');
var token = await box.read("token");
var response = await http.get(url, headers: {"token": token});
var voteResponse = await http.get(voteUrl, headers: {"token": token});
var positive = [];
var negative = [];
var totalPositive = 0;
var totalNegative = 0;
var myBestVote = 0.0;
var myWorstVote = 0.0;
var myBestVotePtg = 0.0;
var myWorstVotePtg = 0.0;
// print(jsonDecode(response.body));
if (response.statusCode == 200) {
// Parse the response body into a map
Map<String, dynamic> responseBody = jsonDecode(response.body);
Map<String, dynamic> voteResponseBody = jsonDecode(voteResponse.body);
// Map the 'profile' data from the response to the 'Profile' model
Profile profileData = Profile.fromJson(responseBody['data']['profile']);
// Assign the 'profile' data to the 'profile' variable
positive = voteResponseBody['data']['positive'];
negative = voteResponseBody['data']['negative'];
profile.value = profileData;
if (profileData.bestCandidateId != null) {
Positive best = Positive.fromJson(responseBody['data']['positive']);
Positive worst = Positive.fromJson(responseBody['data']['negative']);
bestCandidate.value = best;
worstCandidate.value = worst;
if (positive.isNotEmpty && negative.isNotEmpty) {
for (var i = 0; i < positive.length; i++) {
int num = positive[i]['count_all'] ?? 0;
totalPositive += num;
}
for (var i = 0; i < negative.length; i++) {
int num = negative[i]['count_all'] ?? 0;
totalNegative += num;
}
}
Map<String, dynamic>? bestCandidateData;
for (var positiveVote in positive) {
if (positiveVote['best_candidate_id'] == best.candidateId) {
bestCandidateData = positiveVote;
break;
}
}
// Find worst candidate data
Map<String, dynamic>? worstCandidateData;
for (var negativeVote in negative) {
if (negativeVote['worst_candidate_id'] == worst.candidateId) {
worstCandidateData = negativeVote;
break;
}
}
if (bestCandidateData != null) {
double bestCandidateScore =
bestCandidateData['count_all'] / totalPositive;
myBestVote = bestCandidateScore;
// print('Best Candidate Score: $bestCandidateScore');
}
if (worstCandidateData != null) {
double worstCandidateScore =
worstCandidateData['count_all'] / totalNegative;
myWorstVote = worstCandidateScore;
// print('Worst Candidate Score: $worstCandidateScore');
}
myBestVotePtg = myBestVote * 100;
myWorstVotePtg = myWorstVote * 100;
return {
"status": response.statusCode,
"body": responseBody,
"myVote": {
"postive": {
"value": myBestVote,
"stringValue": "${myBestVotePtg.toStringAsFixed(3)}%"
},
"negative": {
"value": myWorstVote,
"stringValue": "${myWorstVotePtg.toStringAsFixed(3)}%"
}
}
};
}
// print(totalPositive);
// print(voteResponseBody['data']['positive']);
return {
"status": response.statusCode,
"body": responseBody,
"myVote": {
"postive": {"value": 0.000, "stringValue": "0.000%"},
"negative": {"value": 0.000, "stringValue": "0.000%"}
}
};
}
// print(bestCandidate.value.toString());
return {
"status": response.statusCode,
"body": jsonDecode(response.body),
};
} catch (e) {
print("Response error get users my vote: $e");
return {"status": 500, "body": "Internal Server Error"};
}
}
}
this is the response from api
{
"success": true,
"object": "candidates",
"data": [
{
"candidate_id": "1",
"candidates_name": "Ganjar",
"candidate_picture": "https://www.pdiperjuanganbali.id/uploads/berita/berita_231609080914_Ganjar,SeorangIdeologisyangBerjiwaKsatria.jpeg",
"candidate_party": "PDIP",
"candidate_description": "Ganjar Pranowo dilahirkan dari keluarga yang sederhana pada tanggal 28 Oktober 1968 lalu di desa lereng Gunung Lawu, Karanganyar. Ganjar Pranowo memiliki nama asli Ganjar Sungkowo yang artinya Ganjaran dari kesusahan atau kesedihan. Namun, saat Ganjar akan memasuki sekolah dasar, nama Sungkowo diganti menjadi Pranowo oleh orang tuanya. Pergantian nama ini terjadi karena rasa ketakutan dari orang tua Ganjar, apabila nama Sungkowo tetap maka sang anak kelak selalu berkubang dengan kesialan dan kesusahan.",
"reasons": [
{
"reason_id": "1",
"candidate_id": "1",
"reason_description": "Walikota",
"is_postive_remarks": true
},
{
"reason_id": "2",
"candidate_id": "1",
"reason_description": "Orang Jawa",
"is_postive_remarks": true
},
{
"reason_id": "3",
"candidate_id": "1",
"reason_description": "Sopan",
"is_postive_remarks": true
}
]
},
{
"candidate_id": "2",
"candidates_name": "Prabowo",
"candidate_picture": "https://asset-2.tstatic.net/manado/foto/bank/images/menteri-pertahanan-prabowo-subianto-23424.jpg",
"candidate_party": "GERINDRA",
"candidate_description": "Jenderal TNI (Purn.) H. Prabowo Subianto Djojohadikusumo merupakan anak ketiga dan putra pertama yang lahir pada tanggal 17 Oktober 1951. Ayahnya bernama Soemitro Djojohadikusumo yang berasal dari Kebumen, Jawa Tengah. Ayah Prabowo merupakan seorang pakar ekonomi dan juga politisi Partai Sosialis Indonesia yang saat itu baru saja selesai menjabat sebagai Menteri Perindustrian di Kabinet Natsir pada April 1952. Sedangkan Ibunya bernama Dora Marie Sigar atau yang dikenal dengan nama Dora Soemitro. Beliau merupakan seorang wanita Kristen Protestan berdarah Minahasa. Ibunya berasal dari keluarga Maengkom di Langowan, Sulawesi Utara.",
"reasons": [
{
"reason_id": "4",
"candidate_id": "2",
"reason_description": "Patriot",
"is_postive_remarks": true
},
{
"reason_id": "5",
"candidate_id": "2",
"reason_description": "Merah Putih darahku",
"is_postive_remarks": true
},
{
"reason_id": "6",
"candidate_id": "2",
"reason_description": "Menhan",
"is_postive_remarks": true
}
]
},
{
"candidate_id": "3",
"candidates_name": "Anies",
"candidate_picture": "https://cdnwpedutorenews.gramedia.net/wp-content/uploads/2022/03/10162611/cropped-scaled-1.jpg",
"candidate_party": "PKS",
"candidate_description": "Anies Baswedan lahir pada tanggal 7 Mei tahun 1969 di Kuningan, Jawa Barat. Ayahnya bernama Rasyid Baswedan yang berprofesi sebagai dosen Fakultas Ekonomi di Universitas Islam Indonesia. Anies merupakan cucu dari H. Abdurrahman Baswedan atau dikenal dengan nama A. R. Baswedan. Kakeknya Anies merupakan pahlawan nasional dan dikenal sebagai seorang nasionalis, jurnalis, pejuang Kemerdekaan Indonesia, diplomat, mubaligh, dan sastrawan Indonesia.",
"reasons": [
{
"reason_id": "7",
"candidate_id": "3",
"reason_description": "Gubernur",
"is_postive_remarks": true
},
{
"reason_id": "8",
"candidate_id": "3",
"reason_description": "Muslim",
"is_postive_remarks": true
},
{
"reason_id": "9",
"candidate_id": "3",
"reason_description": "Dosen",
"is_postive_remarks": true
}
]
}
]
}
2
Answers
If you need get the response as a
List<something>
, first you need define the objects to get the response, for example, you need define some class to get the result, I recomend use DTO Pattern, an for example you can use this classes:You can use JSON to Dart Classes to get the classes using the json response
Then you need get the response as a
Future<Root>
, you could modify your methodgetCandidates()
like this (I put a method from other personal Project but the idea is similar):Finally you could modify your builder and use a
ListView.builder
like the example, where "snapshot.data" is the response from "getCandidates()" methodshould you use it "?" but it must also be combined with "??"
This way when there is no data, candidates have an empty list and you would no longer have the previous error