I've a Quiz app which load text from a List of String class using ListViewBuilder. When i click on a category the question list is taking time to open 3-4 seconds. Here question in QuestionCategory is one for demo purpose. In real app it has 200+ question and the Navigation problem comes where question list is around more than 100+. The related question is already asked but the there is no proper solution for this.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:himachal_gk/Model/question_model.dart';
import 'package:himachal_gk/Widget/style_widgets.dart';
import '../../../Ads/GoogleAds.dart';
import 'category_question.dart';
class CategoryWiseScreen extends StatelessWidget {
const CategoryWiseScreen({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
var category = [
'River',
// 'Books',
];
return Scaffold(
backgroundColor: Colors.grey[200],
appBar: AppBar(
title: const Text("Category Wise GK"),
actions: [
IconButton(
onPressed: () {
Navigator.of(context).pushNamed('/Favorite');
},
icon: const Icon(Icons.favorite_border_rounded))
],
),
bottomNavigationBar: GoogleAds.getBottomBannerAd(context),
body: AnimationLimiter(
child: ListView.builder(
cacheExtent: 9999999,
itemCount: category.length,
itemBuilder: (context, index) {
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 375),
child: SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(
child: Column(
children: <Widget>[
SubCategoryTiles(
categoryIcon: Icons.school,
titleText: Center(
child: Text(
category[index],
style: headingTextStyle,
textAlign: TextAlign.center,
),
),
onTapHandler: () {
if (index == 0) {
List<QuestionCategory> questionCategory = [
QuestionCategory(
question:
'How many rivers are there in Himachal Pradesh',
correctAnswer: 'Five'),
];
Navigator.push(
context,
CupertinoPageRoute(
builder: ((context) => CategoryQuestion(
title: category[index],
questionCategory: questionCategory,
)),
),
);
}
},
),
const Divider(),
],
),
),
),
);
},
),
),
);
}
}
Here is SubCategoryTiles code we have used in above code.
import 'package:flutter/material.dart';
class SubCategoryTiles extends StatelessWidget {
const SubCategoryTiles({
Key? key,
required this.titleText,
required this.onTapHandler,
required this.categoryIcon,
}) : super(key: key);
final Widget titleText;
final VoidCallback onTapHandler;
final IconData categoryIcon;
#override
Widget build(BuildContext context) {
return ListTile(
leading: CircleAvatar(
backgroundColor: Colors.white,
child: Icon(
categoryIcon,
size: 30,
),
),
title: titleText,
trailing: const Icon(Icons.arrow_right),
onTap: onTapHandler,
);
}
}
This is a performance issue. This is caused by two things in your case. First, you are loading heavy data while on a Navigation call. I will advise using initState or an async functionenter code here to call data before pushing to any screen.
List<QuestionCategory> questionCategory = [
QuestionCategory(
question: 'How many rivers are there in Himachal Pradesh',
correctAnswer: 'Five',
),
];
The above shows, you are calling within onTapHandler the same function that pushes to a new screen. In the live app, the Navigation will take time as it has to wait for data to fill in the questionCategory .
Related
I was using a list view builder which got its item count from the hive database. I managed to get it to work but its text appeared to be in brackets for example (Apollo, Jesus) any ideas on how to fix it. as in the formatting so it would appear as apollo,jesus. FYI I opened and initiated the box on the main.dart page
here is my code, I'm still learning sorry for bad code.
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
void main() {
runApp(nextpage());
}
class nextpage extends StatefulWidget {
const nextpage({Key? key}) : super(key: key);
#override
State<nextpage> createState() => _nextpageState();
}
class _nextpageState extends State<nextpage> {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: SafeArea(
child: Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(
Icons.chevron_left,
color: Colors.black,
),
onPressed: () => Navigator.pop(context),
),
backgroundColor: Colors.yellow,
centerTitle: true,
title: Text(
'Database',
style: TextStyle(color: Colors.black),
),
),
backgroundColor: Colors.yellow[200],
body: ListView.builder(
itemCount: Hive.box('db').length,
itemBuilder: (BuildContext context, int index) {
return Container(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
Hive.box('db').values.toString(),
style: TextStyle(fontSize: 20),
),
),
],
),
);
},
),
),
),
);
}
}
I tried changing the text to Hive.box('db').values.toString but it didn't provide results
You need to open the box to receive data. You can do
void main() async {
await Hive.openBox('db');
runApp(nextpage());
}
or use FutureBuilder [.open() is a future method] before ListView.
More about using hivedb
I'm currently writing a flashcard app. The texts in the cards are supplied from a List<String>. Now, I want to make two buttons for tagging card with "Easy" and "Difficult" respectively. From here, I want to be able to categorize them and give the user options to show only those tagged with Easy or only Difficult or etc.
My problem is I don't know how to "connect" the buttons to the List, e.g. how will the button tag the card since each card is generated with a PageView.builder from said List. Is there a way to let the app know which element of the List is currently shown on the card so that the button know what to tag?
I'm lost.
Here is the simplified code of the app.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: const MyHomePage(),
theme: ThemeData(primarySwatch: Colors.deepOrange),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: PageView.builder(
itemCount: KontenModeFlashcards.teksDepan.length,
itemBuilder: (context, index) {
final isiTeksDepan = KontenModeFlashcards.teksDepan[index];
return PageView(
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
isiTeksDepan,
),
],
),
],
);
},
),
);
}
}
// the list of texts that will go in the cards
class KontenModeFlashcards {
static const List<String> teksDepan = [
'text 1',
'text 2',
'text 3',
];
}
Thanks in advance!
First let's fix the data structure, change the type of teksDepan from List<String> to List<Map<String, dynamic>>, so that it will list maps and that can be used to store the "difficulty-level" (you can set the difficulty level to be a bool "isEasy", as well). It will look something like this:
class KontenModeFlashcards {
static const List<Map<String, dynamic>> teksDepan = [
{'text': 'text 1', 'isEasy': true},
{'text': 'text 2', 'isEasy': true},
{'text': 'text 3', 'isEasy': true},
];
}
Now that you have a right structure for the data you can now use that in your UI, like this:
#override
Widget build(BuildContext context) {
return SafeArea(
child: PageView.builder(
itemCount: KontenModeFlashcards.teksDepan.length,
itemBuilder: (BuildContext context, int index) {
String? text = KontenModeFlashcards.teksDepan[index]['text'];
bool isEasy = KontenModeFlashcards.teksDepan[index]['isEasy'] ?? false;
return PageView(
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Card(
child: ListTile(
contentPadding: const EdgeInsets.all(12),
trailing: Icon(
isEasy ? Icons.cake : Icons.anchor,
),
title: Text(text ?? 'No Text was provided'),
subtitle: ElevatedButton.icon(
onPressed: () {
setState(() {
KontenModeFlashcards.teksDepan[index]['isEasy'] = !isEasy;
});
},
icon: isEasy
? const Icon(Icons.anchor)
: const Icon(Icons.cake),
label: Text(isEasy ? 'Difficult' : 'Easy'),
),
),
),
],
),
],
);
},
),
);
}
);
Make sure your MyHomePage is Stateful Widget. Which you can do by right clicking on where it says StatelessWidget (on the same line) and select Refactor... and then Convert to StatefulWidget.
I think that should do it. Let me know if you have any more questions, or if you want me to simply something.
This code include Offstage toggle feature:
#override
Widget build(BuildContext context) {
return SafeArea(
child: PageView.builder(
itemCount: KontenModeFlashcards.teksDepan.length,
itemBuilder: (BuildContext context, int index) {
String? text = KontenModeFlashcards.teksDepan[index]['text'];
bool isEasy = KontenModeFlashcards.teksDepan[index]['isEasy'] ?? false;
return PageView(
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Offstage(
offstage: !isEasy,
child: Card(
child: ListTile(
contentPadding: const EdgeInsets.all(12),
trailing: Icon(
isEasy ? Icons.cake : Icons.anchor,
),
title: Text(text ?? 'No Text was provided'),
),
),
),
ElevatedButton.icon(
onPressed: () {
setState(() {
KontenModeFlashcards.teksDepan[index]['isEasy'] = !isEasy;
});
},
icon: isEasy
? const Icon(Icons.cake)
: const Icon(Icons.anchor),
label: Text(isEasy ? 'Easy' : 'Difficult'),
),
],
),
],
);
},
),
);
}
The reason it hiding when it was set to Difficult was because that how I had set it up in the ElevatedButton widget you can review at by comparing the two code snippets.
I'm trying to figure out this error. although I use the Rocket Guide — part 3 section and although I change a bunch of coding and altered some code to fit my need. I'm getting this error in the id: gunpla.id
making it really hard to fix. I tried to find it and tried some other solutions that could fit but they do not work. I would appreciate if someone could help me and show me the solution for this. this part is implementing the firestore from firebase.
Gunpla gunpla
package:gunpla_database/gunpla_details/gunpla_details_screen.dart
The argument type 'int' can't be assigned to the parameter type 'String'.dart(argument_type_not_assignable)
import 'package:ant_icons/ant_icons.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:gunpla_database/backend/backend.dart';
import 'package:cupertino_icons/cupertino_icons.dart';
import 'package:provider/provider.dart';
class GunplaDetailsScreen extends StatelessWidget {
const GunplaDetailsScreen({
Key key,
#required this.gunpla,
}) : assert(gunpla != null),
super(key: key);
final Gunpla gunpla;
#override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return Scaffold(
appBar: AppBar(
title: Text(gunpla.name),
actions: [
StreamBuilder<List<String>>(
stream: context.read<Backend>().favoritedGunplas,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
}
final favoritedGunplas = snapshot.data;
final isGunplaFavorited = favoritedGunplas.contains(gunpla.id);
return IconButton(
onPressed: () {
context.read<Backend>().setFavoritedGunpla(
id: gunpla.id, <-- Error Is here
favorited: !isGunplaFavorited,
);
},
icon: isGunplaFavorited
? const Icon(
AntIcons.heart,
color: Colors.redAccent,
)
: const Icon(AntIcons.heart_outline),
);
}),
],
),
body: ListView(
children: [
if (gunpla.image.isNotEmpty) _ImageHeader(gunpla: gunpla),
ListTile(
title: Text(
gunpla.name,
style: textTheme.headline6,
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Text(
gunpla.series,
style: textTheme.subtitle1,
),
),
const Divider(),
ListTile(
leading: const Icon(AntIcons.column_width),
title: Text('${gunpla.scale}'),
subtitle: const Text('Available Scale'),
),
const Divider(),
ListTile(
leading: const Icon(AntIcons.colum_height),
title: Text('${gunpla.grade}'),
subtitle: const Text('Available Grade'),
),
const Divider(),
ListTile(
leading: const Icon(CupertinoIcons.star_fill),
title: Text('${gunpla.exclusive}'),
subtitle: const Text('Exclusive'),
),
const Divider(),
Padding(
padding: const EdgeInsets.all(16.0),
child: SizedBox(
height: 56.0,
),
),
],
),
);
}
}
class _ImageHeader extends StatelessWidget {
const _ImageHeader({
Key key,
#required this.gunpla,
}) : super(key: key);
final Gunpla gunpla;
#override
Widget build(BuildContext context) {
return SizedBox(
height: 250,
child: Hero(
tag: 'hero-${gunpla.id}-image',
child: Image.network(
gunpla.image,
fit: BoxFit.cover,
),
),
);
}
}
Code shown is not enough, you might need to add the code for the Gunpla class.
However, you are most probably giving one of these text widgets an integer. Just add .toString() to the int inside the Text widget.
I'm testing out this Flutter project which generates a movie list from a for loop:for (int i = 0; i < 3; i++) {...}.
The result of the loop is 3 cards which I'd like to add an onTap function to and navigate to the corresponding page as a result.
Github:https://github.com/devefy/Flutter-Streaming-Service-App-UI/blob/master/lib/main.dart
After the padding on line 222, I added a ListTile() with the onTap:(){}
widget. This enabled the tap widget for the bottom half of the card.
// Line 219 to 222
Padding(
padding: EdgeInsets.only(top: 3.0),
child: Text(i == 0 ? "Season 2" : ""),
),// Padding
ListTile(onTap: (){
debugPrint('${[i]} was tapped!');
//Navigator.push(context, route)
My results when tapping the 3 cards.
flutter: [0] was tapped!
flutter: [1] was tapped!
flutter: [2] was tapped!
Where I get lost is how to actually navigate to the detail page of the movie depending on which card I tap on.
Any help is appreciated...Thank You All!!!
The best practice for something like this would be to create 2 pages, a movie list, and a details page.
The movie list will loop through all of the movies, then the on tap would point to the details page. The key here is that you can pass data to the details page when navigating. Whether that be an id or slug for the movie allowing you to fetch specific data or just an index to a list for a simpler example.
Navigator.push( context, MaterialPageRoute( builder: (context) => DetailScreen(todo: todos[index]),),);
Here is an example regarding a todo list and a details screen. I would try running this so you can understand further what I mean.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class Todo {
final String title;
final String description;
Todo(this.title, this.description);
}
void main() {
runApp(MaterialApp(
title: 'Passing Data',
home: TodosScreen(
todos: List.generate(
20,
(i) => Todo(
'Todo $i',
'A description of what needs to be done for Todo $i',
),
),
),
));
}
class TodosScreen extends StatelessWidget {
final List<Todo> todos;
TodosScreen({Key key, #required this.todos}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Todos'),
),
body: ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(todos[index].title),
// When a user taps the ListTile, navigate to the DetailScreen.
// Notice that you're not only creating a DetailScreen, you're
// also passing the current todo through to it.
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(todo: todos[index]),
),
);
},
);
},
),
);
}
}
class DetailScreen extends StatelessWidget {
// Declare a field that holds the Todo.
final Todo todo;
// In the constructor, require a Todo.
DetailScreen({Key key, #required this.todo}) : super(key: key);
#override
Widget build(BuildContext context) {
// Use the Todo to create the UI.
return Scaffold(
appBar: AppBar(
title: Text(todo.title),
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Text(todo.description),
),
);
}
}
Here is the app running.
Let me know if you have any questions!
Finally found a solution I was happy with. Thanks to flutter_ui_challenge.
import 'package:flutter/material.dart';
import 'package:flutter_youtube/flutter_youtube.dart';
class YouTubeVideoList extends StatelessWidget {
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text("YouTube Video List"),
backgroundColor: Colors.lightBlue,
elevation: 2,
actions: <Widget>[
Container(
padding: EdgeInsets.all(10),
)
],
),
body: Lists(),
);
}
}
class Item {
final String title;
final String category;
final String place;
final Function onTap;
final String image;
Item(
{this.title,
this.category,
this.place,
this.onTap,
this.image});
}
class Lists extends StatelessWidget {
final List<Item> _data = [
Item(
onTap: playYoutubeVideo1,
title: 'Gardens By the Bay',
category: "Gardens",
place: "Singapore",
image: "https://images.pexels.com/photos/672142/pexels-photo-672142.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940"),
Item(
onTap: playYoutubeVideo2,
title: 'Singapore Zoo',
category: "Parks",
place: "Singapore",
image: "https://images.pexels.com/photos/1736222/pexels-photo-1736222.jpeg?cs=srgb&dl=adult-adventure-backpacker-1736222.jpg&fm=jpg"),
Item(
onTap: playYoutubeVideo3,
title: 'National Orchid Garden',
category: "Parks",
place: "Singapore",
image: "https://images.pexels.com/photos/62403/pexels-photo-62403.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940"),
];
#override
Widget build(BuildContext context) {
return ListView.builder(
padding: EdgeInsets.all(6),
itemCount: _data.length,
itemBuilder: (BuildContext context, int index) {
Item item = _data[index];
return GestureDetector(
onTap: item.onTap,
child: Card(
elevation: 3,
child: Row(
children: <Widget>[
Container(
height: 125,
width: 110,
padding:
EdgeInsets.only(left: 0, top: 10, bottom: 70, right: 20),
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(item.image),
fit: BoxFit.cover)),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
item.title,
style: TextStyle(
color: Colors.deepOrange,
fontWeight: FontWeight.w700,
fontSize: 15),
),
Text(
item.category,
style: TextStyle(fontSize: 12, color: Colors.black87),
),
Text(
item.place,
style: TextStyle(fontSize: 10, color: Colors.black87),
),
SizedBox(
height: 10,
),
],
),
)
],
),
),
);
},
);
}
var youtube = new FlutterYoutube();
static playYoutubeVideo1() {
FlutterYoutube.playYoutubeVideoByUrl(
apiKey: "YOUR_API_KEY",
videoUrl: "YOUTUBE_VIDEO_URL",
);
}
static playYoutubeVideo2() {
FlutterYoutube.playYoutubeVideoByUrl(
apiKey: "YOUR_API_KEY",
videoUrl: "YOUTUBE_VIDEO_URL",
);
}
static playYoutubeVideo3() {
FlutterYoutube.playYoutubeVideoByUrl(
apiKey: "YOUR_API_KEY",
videoUrl: "YOUTUBE_VIDEO_URL",
);
}
}
I am trying to build a pokedex application in flutter. Currently I have created the first screen, with all 151 pokemon, their image, name, and # from a json api call. I am trying to make functionality where when you tap on a specific pokemon from the first screen, a new screen will appear with more details about the pokemon you tapped on. Currently having difficulties setting up my navigation to carry that information over.
Here is my project
import 'package:http/http.dart' as http;
import 'dart:async';
import 'dart:convert';
Map _data;
List _pokemon = [];
void main() async {
_data = await fetchData();
_pokemon = _data['pokemon'];
runApp(
MaterialApp(
title: 'Poke App',
home: new HomePage(),
debugShowCheckedModeBanner: false,
),
);
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
void initState() {
super.initState();
fetchData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Poke App'),
centerTitle: true,
backgroundColor: Colors.cyan,
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
backgroundColor: Colors.cyan,
child: Icon(Icons.search),
),
body: GridView.count(
crossAxisCount: 2,
children: List.generate(_pokemon.length, (index) {
return Padding(
padding: const EdgeInsets.fromLTRB(1.0, 5.0, 1.0, 5.0),
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => new PokeDetails(_pokemon[index]
),
),
);
},
child: Card(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(bottom: 10.0),
child: Container(
height: 100.0,
width: 100.0,
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage('${_pokemon[index]['img']}'),
),
),
),
),
Padding(
padding: const EdgeInsets.only(bottom: 2.0),
child: Text(
'${_pokemon[index]['name']}',
style: TextStyle(
fontSize: 16.0,
fontFamily: 'Chivo',
fontStyle: FontStyle.italic),
),
),
Text(
'${_pokemon[index]['num']}',
style: TextStyle(
fontFamily: 'Indie Flower',
fontWeight: FontWeight.w400,
fontSize: 20.0),
)
],
),
),
),
);
}),
));
}
}
Future<Map> fetchData() async {
String url =
"https://raw.githubusercontent.com/Biuni/PokemonGO-Pokedex/master/pokedex.json";
http.Response response = await http.get(url);
return json.decode(response.body);
}
class PokeDetails extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.cyan,
appBar: AppBar(
title: Text('${_pokemon[index]['name']}'),
centerTitle: true,
backgroundColor: Colors.cyan,
),
);
}
}
I am expecting the correct pokemon to appear on screen 2 (PokeDetails) but i have yet to be able to achieve this
I think you may benefit from reading through some more of the documentation on flutter. Though, to get you moving forward, your PokeDetails class has no way of knowing what to look for when you're sending over the pokemon data... You should create a pokemon class so you can map the api results over to something a little easier to work with. Then you can do something like:
class PokeDetails extends StatelessWidget{
final Pokemon pokemon;
PokeDetails({
#required this.pokemon
});
//now display the pokemon details
}
Side-note, you'll want to avoid using those global variables and functions (such as fetchData, _data, and _pokemon). Those should be in their own classes. Maybe a class containing your fetch function along with a map of the data that you received. This is all kind of the bare minimum to get your feet wet. Happy coding!