I want to use this flutter treeview widget in my flutter app to build companies treeview
https://pub.dev/packages/tree_view
i have a webservice with list of companies in a tree structure.
https://washservice.com/api/companyXML/1fe5bae2-331a-4080-b34f-5ebd3518efd8
I have written json parsing code with recursive function to build treeview but it is not working .can someone help me to fix parsing issue and build treeview widget
Here is my code
import 'dart:async';
import 'dart:convert';
import 'package:example/models/Company.dart';
import 'package:example/widgets/directory_widget.dart';
import 'package:example/widgets/file_widget.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:tree_view/tree_view.dart';
class CompaniesPage extends StatefulWidget {
CompaniesPage({Key key, this.title}) : super(key: key);
final String title;
#override
_CompaniesPageState createState() => _CompaniesPageState();
}
class _CompaniesPageState extends State<CompaniesPage> {
List<Company> companiesList = new List<Company>();
#override
void initState() {
super.initState();
// Loading initial data or first request to get the data
_getTeeViewData1();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title ?? 'Tree View demo'),
),
body: Center(
child: TreeView(
startExpanded: false,
children: _getChildList(companiesList),
),
),
);
}
// Webservice request to load 20 users data using paging
Future<List<Company>> _getTeeViewData1() async {
String url =
"https://washservice.com/api/companyXML/1fe5bae2-331a-4080-b34f-5ebd3518efd8";
print(url);
var response = await http.get(url);
var jsonData = json.decode(response.body);
print(jsonData);
var data = jsonData["Companies"];
var companies = data["Company"];
print(companies);
Company c = new Company();
c.CompanyId = companies["CompanyId"];
c.CompanyName = companies["CompanyName"];
c.ParentId = companies["ParentId"];
c.CostCenter = '${companies["CostCenter"] ?? ""}';
c.IsSelectableforMovement = companies["IsSelectableforMovement"];
c = getChildCompanies(companies["Company"], c);
companiesList.add(c);
return companiesList;
}
Company getChildCompanies(childCompanies, parentCompany) {
if (childCompanies != null) {
for (var childCompany in childCompanies) {
Company childCO = new Company();
childCO.CompanyId = childCompany["CompanyId"];
childCO.CompanyName = childCompany["CompanyName"];
childCO.ParentId = childCompany["ParentId"];
childCO.CostCenter = '${childCompany["CostCenter"] ?? ""}';
childCO.IsSelectableforMovement =
childCompany["IsSelectableforMovement"];
Company c2 = getChildCompanies(childCompany["Company"], childCO);
parentCompany.company.add(c2);
return parentCompany;
}
}
}
List<Widget> _getChildList(List<Company> childDocuments) {
return childDocuments.map((document) {
if (document.company.length != 0) {
return Container(
margin: EdgeInsets.only(left: 8),
child: TreeViewChild(
parent: _getDocumentWidget(document: document),
children: _getChildList(document.company),
),
);
}
return Container(
margin: const EdgeInsets.only(left: 4.0),
child: _getDocumentWidget(document: document),
);
}).toList();
}
Widget _getDocumentWidget({#required Company document}) =>
document.company.length == 0
? _getFileWidget(document: document)
: _getDirectoryWidget(document: document);
DirectoryWidget _getDirectoryWidget({#required Company document}) =>
DirectoryWidget(directoryName: document.CompanyName);
FileWidget _getFileWidget({#required Company document}) =>
FileWidget(fileName: document.CompanyName);
}
Company.dart
class Company {
Company();
String CompanyId;
String CompanyName;
String ParentId;
String CostCenter;
String IsSelectableforMovement;
List<Company> company = new List<Company>();
}
I used the same package with my own json data. Here you can find a sample of usage. Maybe you can adapt it for your use.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:tree_view/tree_view.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'title',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: '/',
routes: {
'/': (context) => TestPage(),
},
);
}
}
class TestPage extends StatefulWidget {
#override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
String responseBody =
'{ "id": 0,"name": "A","children": [{ "id": 1, "name": "Aa","children": [{"id": 2,"name": "Aa1","children": null}]},{ "id": 3, "name": "Ab","children": [{"id": 4,"name": "Ab1","children": null},{"id": 5,"name": "Ab2","children": null}]}]}';
#override
Widget build(BuildContext context) {
Map mapBody = jsonDecode(responseBody);
return SafeArea(
child: Scaffold(
body: printGroupTree(
mapBody,
),
),
);
}
Widget printGroupTree(
Map group, {
double level = 0,
}) {
if (group['children'] != null) {
List<Widget> subGroups = List<Widget>();
for (Map subGroup in group['children']) {
subGroups.add(
printGroupTree(
subGroup,
level: level + 1,
),
);
}
return Parent(
parent: _card(
group['name'],
level * 20,
),
childList: ChildList(
children: subGroups,
),
);
} else {
return _card(
group['name'],
level * 20,
);
}
}
Widget _card(
String groupName,
double leftPadding,
) {
return Container(
padding: EdgeInsets.only(
left: leftPadding + 5,
right: 20,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50.0),
),
height: 100,
child: Row(
children: <Widget>[
Container(
width: 250,
child: Row(
children: <Widget>[
Container(
height: 70,
width: 70,
decoration: BoxDecoration(
shape: BoxShape.rectangle,
image: DecorationImage(
fit: BoxFit.fill,
image: NetworkImage(
'https://upload.wikimedia.org/wikipedia/commons/thumb/a/a6/Rubik%27s_cube.svg/220px-Rubik%27s_cube.svg.png',
),
),
),
),
SizedBox(
width: 10,
),
Flexible(
child: Text(
'SomeText',
),
),
],
),
),
Expanded(
child: SizedBox(),
),
InkWell(
//TODO:Empty method here
onTap: () {},
child: Icon(
Icons.group_add,
size: 40,
),
)
],
),
);
}
}
Related
I've tried this code for searching jobs in listview but the data is not shown in listview. I think JSON is not parsing properly for Jobs data.
Here is the code of the model:
import 'package:flutter/material.dart';
class JobItem {
final String title;
final String description;
JobItem(
{
required this.title,
required this.description,
});
factory JobItem.fromJson(Map<String, dynamic> json) {
return JobItem(
title: json['title'] as String,
description: json['description'] as String,
);
}
}
Here I've written code for the main file to search data from the listview.
List<JobItem> users = [];
List<JobItem> filteredUsers = [];
static String url = 'https://hospitality92.com/api/jobsbycategory/All';
static Future<List<JobItem>> getJobsData() async {
try {
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
List<JobItem> list = parseAgents(response.body);
return list;
} else {
throw Exception('Error');
}
} catch (e) {
throw Exception(e.toString());
}
}
static List<JobItem> parseAgents(String responseBody) {
final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
return parsed.map<JobItem>((json) => JobItem.fromJson(json)).toList();
}
#override
void initState() {
super.initState();
getJobsData().then((usersFromServer) {
setState(() {
users = usersFromServer;
filteredUsers = users;
});
});
}```
Try below code your problem has been solved :
//declare packages
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:http/http.dart' as http;
class Jobs extends StatefulWidget {
Jobs() : super();
#override
JobsState createState() => JobsState();
}
class Debouncer {
final int milliseconds;
VoidCallback action;
Timer _timer;
Debouncer({this.milliseconds});
run(VoidCallback action) {
if (null != _timer) {
_timer.cancel();
}
_timer = Timer(Duration(milliseconds: milliseconds), action);
}
}
class JobsState extends State<Jobs> {
final _debouncer = Debouncer(milliseconds: 500);
List<Subject> subjects = [];
List<Subject> filteredSubjects = [];
//API call for All Subject List
static String url = 'https://hospitality92.com/api/jobsbycategory/All';
static Future<List<Subject>> getAllSubjectsList() async {
try {
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
print(response.body);
List<Subject> list = parseAgents(response.body);
return list;
} else {
throw Exception('Error');
}
} catch (e) {
throw Exception(e.toString());
}
}
static List<Subject> parseAgents(String responseBody) {
final parsed =
json.decode(responseBody)['jobs'].cast<Map<String, dynamic>>();
return parsed.map<Subject>((json) => Subject.fromJson(json)).toList();
}
#override
void initState() {
super.initState();
getAllSubjectsList().then((subjectFromServer) {
setState(() {
subjects = subjectFromServer;
filteredSubjects = subjects;
});
});
}
//Main Widget
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'All Subjects',
style: TextStyle(fontSize: 25),
),
),
body: Column(
children: <Widget>[
//Search Bar to List of typed Subject
Container(
padding: EdgeInsets.all(15),
child: TextField(
textInputAction: TextInputAction.search,
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.0),
borderSide: BorderSide(
color: Colors.grey,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(20.0),
borderSide: BorderSide(
color: Colors.blue,
),
),
suffixIcon: InkWell(
child: Icon(Icons.search),
),
contentPadding: EdgeInsets.all(15.0),
hintText: 'Search ',
),
onChanged: (string) {
_debouncer.run(() {
setState(() {
filteredSubjects = subjects
.where((u) => (u.title
.toLowerCase()
.contains(string.toLowerCase())))
.toList();
});
});
},
),
),
//Lists of Subjects
Expanded(
child: ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
padding: EdgeInsets.only(top: 20, left: 20, right: 20),
itemCount: filteredSubjects.length,
itemBuilder: (BuildContext context, int index) {
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
side: BorderSide(
color: Colors.grey[300],
),
),
child: Padding(
padding: EdgeInsets.all(5.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
ListTile(
leading: Text(
filteredSubjects[index].skills,
),
title: Text(
filteredSubjects[index].title,
style: TextStyle(fontSize: 16),
),
trailing: Text(filteredSubjects[index].position.toString()),
)
],
),
),
);
},
),
),
],
),
);
}
}
//Declare Subject class for json data or parameters of json string/data
//Class For Subject
class Subject {
String title;
int id;
String skills;
String position;
Subject({
this.id,
this.title,
this.skills,
this.position,
});
factory Subject.fromJson(Map<String, dynamic> json) {
return Subject(
title: json['title'] as String,
id: json['id'],
skills: json['skills'],
position: json['positions']);
}
}
Your screen before search:
Your Screen after search :
U have a little mistake in parsing. I added response.body['jobs'].
static Future<List<JobItem>> getJobsData() async {
try {
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
List<JobItem> list = parseAgents(Map<String, dynamic>.from(jsonDecode(response.body))['jobs']);
return list;
} else {
throw Exception('Error');
}
} catch (e) {
throw Exception(e.toString());
} }
I am trying to create a web scraper for a single website picking out just a title, Image and link to the website.
The title comes out fine but the image and link are not properly working can anyone please help me out in this.Here is my code and dependencies I used in .yaml
import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:html/dom.dart' as dom;
import 'package:html/parser.dart' as parser;
import 'package:http/http.dart' as http;
import 'package:url_launcher/url_launcher.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> title;
List<String> image;
List<String> link;
#override
void initState() {
_getdataFromWeb();
}
void _getdataFromWeb() async {
final response = await http.get('https://www.bewakoof.com/men-shirts');
dom.Document document = parser.parse(response.body);
final pictures = document.getElementsByClassName('productGrid');
final description = document.getElementsByClassName('productCardDetail');
final nextPage =
//document.getElementsByClassName('entry-title');
document.getElementsByClassName('col-sm-4 col-xs-6');
image = pictures
.map((element) =>
element.getElementsByTagName("img")[0].attributes['src'])
.toList();
title = description
.map((element) => element.getElementsByTagName("h3")[0].innerHtml)
.toList();
link = nextPage
.map((element) => element.getElementsByTagName("a")[0].attributes['href'])
.toList();
print(link);
}
#override
Widget build(BuildContext context) {
print("hello");
if (image == null)
print("null");
else
print(image);
return SafeArea(
child: Scaffold(
backgroundColor: Colors.black87,
body: title == null || title.length == 0
? Text(
"No data",
style: TextStyle(
color: Colors.white,
),
)
: ListView.builder(
itemCount: title.length,
itemBuilder: (context, index) {
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 375),
child: SlideAnimation(
child: FadeInAnimation(
child: GestureDetector(
onTap: () async {
dynamic url = link[index];
if (await canLaunch(url))
launch(url);
else {
print('error');
}
},
child: Padding(
padding: const EdgeInsets.all(10),
child: Card(
child: Container(
color: Colors.black87,
child: Column(
children: <Widget>[
Align(
alignment: Alignment.centerLeft,
child: Text(
title[index],
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.cyan,
fontSize: 20,
),
),
),
SizedBox(height: 15),
Image.network(image[0]),
],
),
),
),
),
),
),
),
);
},
),
),
);
}
}
cupertino_icons: ^1.0.1
http: ^0.12.0+4
html: ^0.14.0+3
flutter_staggered_animations: "^0.1.2"
url_launcher: ^5.4.0
I may need it tomorrow if it can be possible
enter image description here
Here I added the html pic for the elements rest part I debugged only the link is not working.
Here is the debugged code:
import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:html/dom.dart' as dom;
import 'package:html/parser.dart' as parser;
import 'package:http/http.dart' as http;
import 'package:url_launcher/url_launcher.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> title;
List<String> image;
List<String> link;
#override
void initState() {
_getdataFromWeb();
}
void _getdataFromWeb() async {
final response = await http.get(
'https://www.bewakoof.com/');
dom.Document document = parser.parse(response.body);
final pictures =
document.getElementsByClassName('chWrprInner');
//document.getElementsByClassName('entry-header blog-entry-header');
final description =
//document.getElementsByClassName('entry-content');
document.getElementsByClassName('chWrprInner');
final nextPage =
//document.getElementsByClassName('entry-title');
document.getElementsByClassName('chWrprInner');
image = pictures
.map((element) =>
element.getElementsByTagName("img")[0]
.attributes['src'])
.toList();
title = description
.map((element) => element.getElementsByTagName("p")[0]
.innerHtml)
.toList();
link = nextPage
.map((element) =>
element.getElementsByTagName("a")[0]
.attributes['href'])
.toList();
print(link);
}
#override
Widget build(BuildContext context) {
print("hello");
if (image == null)
print("null");
else
print(image);
return SafeArea(
child: Scaffold(
backgroundColor: Colors.black87,
body: title == null || title.length == 0
? Text(
"No data",
style: TextStyle(
color: Colors.white,
),
)
: ListView.builder(
itemCount: title.length,
itemBuilder: (context, index) {
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 375),
child: SlideAnimation(
child: FadeInAnimation(
child: GestureDetector(
onTap: () async {
dynamic url = link[index];
if (await canLaunch(url))
launch(url);
else {
print('error');
}
},
child: Padding(
padding: const EdgeInsets.all(10),
child: Card(
child: Container(
color: Colors.black87,
child: Column(
children: <Widget>[
Align(
alignment: Alignment.centerLeft,
child: Text(
title[index],
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.cyan,
fontSize: 20,
),
),
),
SizedBox(height: 15),
Text(
title[index],
style: TextStyle(
color: Colors.white,
),
),
Image.network(image[index]),
],
),
),
),
),
))),
);
},
),
),
);
}
}
you can use this package for web scraping:
web_scraper: ^0.0.9
if need see sample , you can visit this repository:
https://github.com/esmaeil-ahmadipour/Flutter_Web_Scraper
I'm trying to add new content into a json as a map. It should look something like this:-
[
{
"title":"mon",
"date":"2/3/2020"
},
{
"title":"tue",
"date":"3/3/2020"
},
{
"title":"wed",
"date":"4/3/2020"
}
]
And I want show it in a listview. This is the code I use to create, write, and display:-
//new class
class WorkersLog extends StatefulWidget {
#override
_WorkersLogState createState() => _WorkersLogState();
}
class _WorkersLogState extends State<WorkersLog> {
#override
Widget build(BuildContext context) {
void _showSettingsPanel() {
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (context) {
return Container(
height: MediaQuery.of(context).size.height * .90,
padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 40.0),
child: Center(child: WorkerCardData()),
);
});
}
return Container(
child: Column(
children: [
SizedBox(height: 40),
Padding(
padding: EdgeInsets.all(20.0),
child: Center(
child: Text(
"add document to store wages, loans etc of workers save paper,don't lose track of your accounts!"),
),
),
Row(
children: <Widget>[
Center(
child: Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(150, 350, 0, 5),
child: FloatingActionButton.extended(
label: Text('add document'),
icon: Icon(Icons.save),
backgroundColor: Colors.greenAccent,
elevation: 5.0,
onPressed: () {
print('object');
_showSettingsPanel();
},
),
),
],
),
),
],
),
],
),
);
}
}
//Create and write file to json
class WorkerCardData extends StatefulWidget {
#override
_WorkerCardDataState createState() => _WorkerCardDataState();
}
class _WorkerCardDataState extends State<WorkerCardData> {
TextEditingController title = new TextEditingController();
File jsonFile;
Directory dir;
String fileName = 'myFile.json';
bool fileExists = false;
Map<String, dynamic> fileContent;
#override
void initState() {
super.initState();
getApplicationDocumentsDirectory().then((Directory directory) {
dir = directory;
jsonFile = new File(dir.path + '/' + fileName);
fileExists = jsonFile.existsSync();
if (fileExists)
this.setState(
() => fileContent = json.decode(jsonFile.readAsStringSync()));
});
}
#override
void dispose() {
title.dispose();
super.dispose();
}
void createFile(
Map<String, dynamic> content, Directory dir, String fileName) {
print("Creating file!");
File file = new File(dir.path + "/" + fileName);
file.createSync();
fileExists = true;
file.writeAsStringSync(json.encode(content));
}
void writeToFile(String title, dynamic date) {
print("Writing to file!");
Map<String, dynamic> content = {'title': title, 'date': date};
if (fileExists) {
print("File exists");
Map<String, dynamic> jsonFileContent =
json.decode(jsonFile.readAsStringSync());
jsonFileContent.addAll(content);
jsonFile.writeAsStringSync(json.encode(jsonFileContent));
} else {
print("File does not exist!");
createFile(content, dir, fileName);
}
this.setState(() => fileContent = json.decode(jsonFile.readAsStringSync()));
print(fileContent);
}
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
new Padding(padding: new EdgeInsets.only(top: 10.0)),
new Text(
"File content: ",
style: new TextStyle(fontWeight: FontWeight.bold),
),
new Text(fileContent.toString()),
new Padding(padding: new EdgeInsets.only(top: 10.0)),
new Text("Add to JSON file: "),
new TextField(
controller: title,
decoration: textInputDecoration.copyWith(
hintText: 'title',
prefixIcon: Icon(Icons.info, color: Colors.white)),
),
new Padding(padding: new EdgeInsets.only(top: 20.0)),
new RaisedButton(
child: new Text("save new document"),
onPressed: () => writeToFile(title.text, DateTime.now().toString()),
)
],
),
);
}
}
//display json as list view
class WorkerCard extends StatefulWidget {
final String title;
WorkerCard({this.title});
#override
_WorkerCardState createState() => _WorkerCardState();
}
Directory dir;
Map<String, dynamic> workerFileContent;
File workerFile;
class _WorkerCardState extends State<WorkerCard> {
#override
void initState() {
super.initState();
getApplicationDocumentsDirectory().then((Directory directory) {
dir = directory;
workerFile = File(dir.path + 'myFile.json');
workerFileContent = json.decode(workerFile.readAsStringSync());
});
}
#override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) {
return Card(
child: Column(
children: [
Text(workerFileContent['title'].toString()),
Text(workerFileContent['date'].toString()),
],
),
);
},
);
}
}
Here's my problem. Each time I add new title, the file gets overwritten. How do I add a new map to my json file without overwriting previously added content?
loganrussell48 Thanks for guiding me to the answer
Change:-
Map<String, dynamic> fileContent;
To:-
List fileContent;
I've generated a listView from the Pokemon API of a list of Pokemon, I then have onTap events to trigger a new page/class, where I'm passing the name and URL from the API to the new secondPage Class/Screen.
I need to make a second request in this new page because the API Url needs to change to grab specific details but my request seems to be timing out..
Here is my code: If loaded into a new project the first screen should function fine, loading a bunch of Pokemon and their API specific URLs into a listView.
I can successfully pass the name and URL onto the second screen because they do appear in the Appbar.
However when loading the new json data it seems to be timing out without any error.
Does anyone have any advice for a newbie trying to get his footing?
import 'dart:async';
import 'dart:convert';
import 'package:basic_utils/basic_utils.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController editingController = TextEditingController();
String url = 'https://pokeapi.co/api/v2/pokemon/?limit=151';
List data;
Future<String> makeRequest() async {
var response = await http
.get(Uri.encodeFull(url), headers: {"Accept": "application/json"});
setState(() {
var extractData = json.decode(response.body);
data = extractData["results"];
});
}
#override
void initState() {
this.makeRequest();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Pokemon List'),
),
body: Container(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(15.0),
child: new TextField(
onChanged: (value) {
},
decoration: InputDecoration(
labelText: "Search",
hintText: "Search",
contentPadding: const EdgeInsets.all(10.0),
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(6.0))
),
),
),
),
Expanded(
child: new ListView.builder(
itemCount: data == null ? 0 : data.length,
itemBuilder: (BuildContext context, i) {
return new ListTile(
title: new Text(StringUtils.capitalize(data[i]["name"])),
subtitle: new Text(data[i]["url"]),
// leading: new CircleAvatar(
// backgroundImage:
// new NetworkImage(data[i]["picture"]["thumbnail"]),
// ),
onTap: () {
Navigator.push(
context,
new MaterialPageRoute(
builder: (BuildContext context) =>
new SecondPage(data[i])
)
);
},
);
}
),
),
],
),
),
);
}
}
// Class for getting Specific Details on SecondPage
class Post {
final String name;
final int weight;
Post({this.name, this.weight});
factory Post.fromJson(Map<String, dynamic> json) {
return Post(
name: json['name'],
weight: json['weight'],
);
}
}
// New Request for Specific Details
class SecondPage extends StatelessWidget {
SecondPage(this.data);
final data;
Future<Post> fetchPost() async {
final response =
await http.get('https://pokeapi.co/api/v2/pokemon/' + data["name"]);
if (response.statusCode == 200) {
// If the call to the server was successful, parse the JSON.
return Post.fromJson(json.decode(response.body));
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load post');
}
}
Future<Post> post;
#override
void initState() {
fetchPost();
post = fetchPost();
}
#override
Widget build(BuildContext context) =>
new Scaffold(
appBar: new AppBar(
title: new Text(data["name"] + ' - ' + data["url"])),
body: new Center(
child: Column(
children: <Widget>[
Container(
padding: const EdgeInsets.all(30.0),
child: FutureBuilder<Post>(
future: post,
builder: (context, snapshot) {
if (snapshot.hasData) {
Text(snapshot.data.name);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// By default, show a loading spinner.
return CircularProgressIndicator();
},
),
),
],
),
)
);
}
Man, I rewrite your code as I like
import 'dart:async';
import 'dart:convert';
//import 'package:basic_utils/basic_utils.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController editingController = TextEditingController();
String url = 'https://pokeapi.co/api/v2/pokemon/?limit=151';
List data;
Future<String> makeRequest() async {
var response = await http.get(Uri.encodeFull(url), headers: {"Accept": "application/json"});
setState(() {
var extractData = json.decode(response.body);
data = extractData["results"];
});
}
#override
void initState() {
super.initState();
makeRequest();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Pokemon List'),
),
body: Container(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(15.0),
child: TextField(
onChanged: (value) {},
decoration: InputDecoration(
labelText: "Search",
hintText: "Search",
contentPadding: const EdgeInsets.all(10.0),
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(6.0))),
),
),
),
Expanded(
child: ListView.builder(
itemCount: data == null ? 0 : data.length,
itemBuilder: (BuildContext context, i) {
return ListTile(
title: Text(data[i]["name"].toString().toUpperCase()),
subtitle: Text(data[i]["url"]),
// leading: CircleAvatar(
// backgroundImage:
// NetworkImage(data[i]["picture"]["thumbnail"]),
// ),
onTap: () {
Navigator.push(
context, MaterialPageRoute(builder: (BuildContext context) => SecondPage(data[i])));
},
);
}),
),
],
),
),
);
}
}
class SecondPage extends StatefulWidget {
Map data;
SecondPage(this.data);
_SecondState createState() => _SecondState();
}
class _SecondState extends State<SecondPage> {
#override
void initState() {
super.initState();
_fetchPost();
}
Map post;
bool isLoad = true;
_fetchPost() async {
setState(() {
isLoad = true;
});
var url = widget.data["url"];
debugPrint(url);
final response = await http.get(url);
if (response.statusCode == 200) {
// If the call to the server was successful, parse the JSON.
post = json.decode(response.body.toString());
setState(() {
isLoad = false;
});
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load post');
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.data["name"] + ' - ' + widget.data["url"])),
body: _buildPokemon(context),
);
}
Widget _buildPokemon(BuildContext context) {
if (isLoad) return Center(child: CircularProgressIndicator());
return Container(
padding: EdgeInsets.all(10),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(post['name']),
Text(post['weight'].toString()),
Text(post['height'].toString()),
Image.network(post['sprites']['front_default'])
],
),
);
}
}
So I have been learning flutter in a while and I am stuck in this. Sorry if it is a noobish question. I am currently trying to build something like a Card Tab. The information and widget will be stored in a card.
Imagine something like Tinder, where they have multiple card stack and swipe left and right to navigate.
I plan to create that but I cannot seems to find a way to add/render a new card with a button.
It's like adding something to the list, Flutter will use a ListView builder where we add to the list. But there is no TabBarView builder. Is this something that is not possible to do? I try putting a list inside a tab but it's still wont be the same.
I created some basic skeleton here to help convey my meaning. So the card will be swipe left and right and there is a button in the appBar to add card. Lenght is 2 now and I wanted the button to render the 3rd card. Is this possible?
Thanks in advance!
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
home: new CardStack(),
));
}
class CardStack extends StatefulWidget {
#override
_MainState createState() => new _MainState();
}
class _MainState extends State<CardStack> with SingleTickerProviderStateMixin {
TabController _cardController;
#override
void initState() {
super.initState();
_cardController = new TabController(vsync: this, length: 2);
}
#override
void dispose() {
_cardController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: Colors.grey[300],
appBar: new AppBar(
actions: <Widget>[
new IconButton(
icon: const Icon(Icons.add),
tooltip: 'Add Tabs',
onPressed: null,
),
],
title: new Text("Title Here"),
bottom: new PreferredSize(
preferredSize: const Size.fromHeight(20.0),
child: new Theme(
data: Theme.of(context).copyWith(accentColor: Colors.grey),
child: new Container(
height: 50.0,
alignment: Alignment.center,
child: new TabPageSelector(controller: _cardController),
),
)
)
),
body: new TabBarView(
controller: _cardController,
children: <Widget>[
new Center(
child: new Card(
child: new Container(
height: 450.0,
width: 300.0,
child: new IconButton(
icon: new Icon(Icons.favorite, size: 100.0),
tooltip: 'Favorited',
onPressed: null,
)
),
),
),
new Center(
child: new Card(
child: new Container(
height: 450.0,
width: 300.0,
child: new IconButton(
icon: new Icon(Icons.local_pizza, size: 50.0,),
tooltip: 'Pizza',
onPressed: null,
)
),
),
),
],
),
);
}
}
Problems arise if you need to modify the arrays. They consist in the fact that when modifying an array you do not have the opportunity to use the same controller.
You can use the next custom widget for this case:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
#override
MyHomePageState createState() => MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
List<String> data = ['Page 0', 'Page 1', 'Page 2'];
int initPosition = 1;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: CustomTabView(
initPosition: initPosition,
itemCount: data.length,
tabBuilder: (context, index) => Tab(text: data[index]),
pageBuilder: (context, index) => Center(child: Text(data[index])),
onPositionChange: (index) {
print('current position: $index');
initPosition = index;
},
onScroll: (position) => print('$position'),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
data.add('Page ${data.length}');
});
},
child: const Icon(Icons.add),
),
);
}
}
class CustomTabView extends StatefulWidget {
const CustomTabView({
super.key,
required this.itemCount,
required this.tabBuilder,
required this.pageBuilder,
this.stub,
this.onPositionChange,
this.onScroll,
this.initPosition,
});
final int itemCount;
final IndexedWidgetBuilder tabBuilder;
final IndexedWidgetBuilder pageBuilder;
final Widget? stub;
final ValueChanged<int>? onPositionChange;
final ValueChanged<double>? onScroll;
final int? initPosition;
#override
CustomTabsState createState() => CustomTabsState();
}
class CustomTabsState extends State<CustomTabView>
with TickerProviderStateMixin {
late TabController controller;
late int _currentCount;
late int _currentPosition;
#override
void initState() {
_currentPosition = widget.initPosition ?? 0;
controller = TabController(
length: widget.itemCount,
vsync: this,
initialIndex: _currentPosition,
);
controller.addListener(onPositionChange);
controller.animation!.addListener(onScroll);
_currentCount = widget.itemCount;
super.initState();
}
#override
void didUpdateWidget(CustomTabView oldWidget) {
if (_currentCount != widget.itemCount) {
controller.animation!.removeListener(onScroll);
controller.removeListener(onPositionChange);
controller.dispose();
if (widget.initPosition != null) {
_currentPosition = widget.initPosition!;
}
if (_currentPosition > widget.itemCount - 1) {
_currentPosition = widget.itemCount - 1;
_currentPosition = _currentPosition < 0 ? 0 : _currentPosition;
if (widget.onPositionChange is ValueChanged<int>) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted && widget.onPositionChange != null) {
widget.onPositionChange!(_currentPosition);
}
});
}
}
_currentCount = widget.itemCount;
setState(() {
controller = TabController(
length: widget.itemCount,
vsync: this,
initialIndex: _currentPosition,
);
controller.addListener(onPositionChange);
controller.animation!.addListener(onScroll);
});
} else if (widget.initPosition != null) {
controller.animateTo(widget.initPosition!);
}
super.didUpdateWidget(oldWidget);
}
#override
void dispose() {
controller.animation!.removeListener(onScroll);
controller.removeListener(onPositionChange);
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
if (widget.itemCount < 1) return widget.stub ?? Container();
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
alignment: Alignment.center,
child: TabBar(
isScrollable: true,
controller: controller,
labelColor: Theme.of(context).primaryColor,
unselectedLabelColor: Theme.of(context).hintColor,
indicator: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Theme.of(context).primaryColor,
width: 2,
),
),
),
tabs: List.generate(
widget.itemCount,
(index) => widget.tabBuilder(context, index),
),
),
),
Expanded(
child: TabBarView(
controller: controller,
children: List.generate(
widget.itemCount,
(index) => widget.pageBuilder(context, index),
),
),
),
],
);
}
void onPositionChange() {
if (!controller.indexIsChanging) {
_currentPosition = controller.index;
if (widget.onPositionChange is ValueChanged<int>) {
widget.onPositionChange!(_currentPosition);
}
}
}
void onScroll() {
if (widget.onScroll is ValueChanged<double>) {
widget.onScroll!(controller.animation!.value);
}
}
}
Try this.
To make dynamic tab you can use a List and keep appending the list on every button click.
Trick: Clear List and redraw an empty widget and again draw the widgets as per your list.
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
home: new CardStack(),
));
}
class DynamicTabContent {
IconData icon;
String tooTip;
DynamicTabContent.name(this.icon, this.tooTip);
}
class CardStack extends StatefulWidget {
#override
_MainState createState() => new _MainState();
}
class _MainState extends State<CardStack> with TickerProviderStateMixin {
List<DynamicTabContent> myList = new List();
TabController _cardController;
TabPageSelector _tabPageSelector;
#override
void initState() {
super.initState();
myList.add(new DynamicTabContent.name(Icons.favorite, "Favorited"));
myList.add(new DynamicTabContent.name(Icons.local_pizza, "local pizza"));
_cardController = new TabController(vsync: this, length: myList.length);
_tabPageSelector = new TabPageSelector(controller: _cardController);
}
#override
void dispose() {
_cardController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: Colors.grey[300],
appBar: new AppBar(
actions: <Widget>[
new Padding(
padding: const EdgeInsets.all(1.0),
child: new IconButton(
icon: const Icon(
Icons.add,
size: 30.0,
color: Colors.white,
),
tooltip: 'Add Tabs',
onPressed: () {
List<DynamicTabContent> tempList = new List();
myList.forEach((dynamicContent) {
tempList.add(dynamicContent);
});
setState(() {
myList.clear();
});
if (tempList.length % 2 == 0) {
myList.add(new DynamicTabContent.name(Icons.shopping_cart, "shopping cart"));
} else {
myList.add(new DynamicTabContent.name(Icons.camera, "camera"));
}
tempList.forEach((dynamicContent) {
myList.add(dynamicContent);
});
setState(() {
_cardController = new TabController(vsync: this, length: myList.length);
_tabPageSelector = new TabPageSelector(controller: _cardController);
});
},
),
),
],
title: new Text("Title Here"),
bottom: new PreferredSize(
preferredSize: const Size.fromHeight(10.0),
child: new Theme(
data: Theme.of(context).copyWith(accentColor: Colors.grey),
child: myList.isEmpty
? new Container(
height: 30.0,
)
: new Container(
height: 30.0,
alignment: Alignment.center,
child: _tabPageSelector,
),
))),
body: new TabBarView(
controller: _cardController,
children: myList.isEmpty
? <Widget>[]
: myList.map((dynamicContent) {
return new Card(
child: new Container(
height: 450.0,
width: 300.0,
child: new IconButton(
icon: new Icon(dynamicContent.icon, size: 100.0),
tooltip: dynamicContent.tooTip,
onPressed: null,
)),
);
}).toList(),
),
);
}
}
Hope this helps :)