I wanted to ask this certain question, I don't know if I'm being silly or not.
[https://mobile.alphacoders.com/by-category/10?page=1&quickload=1]
here you can see in the link "page=1", I want to create a button in my flutter app so that when I click on the button the page number increases with one digit
so like page=1+1
I think I can use the '$' property in the URL and add a setState but the trickier thing is I'm trying to scrape a website's wallpaper so when the user clicks on the next page button he must be redirected to a new page with new wallpapers
class Top2 extends StatefulWidget {
const Top2({Key? key}) : super(key: key);
#override
State<Top2> createState() => _Top2State();
}
class _Top2State extends State<Top2> {
late List<Map<String, dynamic>> top2Wall;
bool top2Loaded = false;
void top2Fetch() async {
final top2Scraper = WebScraper('https://mobile.alphacoders.com');
if (await top2Scraper.loadWebPage('/by-category/3?page=1')) {
top2Wall = top2Scraper.getElement(
'div.container-masonry > div.item > a > img.img-responsive',
['src', 'title']);
// ignore: avoid_print
print(top2Wall);
setState(() {
top2Loaded = true;
});
}
}
#override
void initState() {
super.initState();
top2Fetch();
}
#override
Widget build(BuildContext context) {
Size screenSize = MediaQuery.of(context).size;
return Scaffold(
body: top2Loaded
// ignore: sized_box_for_whitespace
? Container(
height: screenSize.height,
width: double.infinity,
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Wrap(children: [
for (int i = 1; i < top2Wall.length; i++)
WallCard(src: top2Wall[i]['attributes']['src'])
]),
),
)
: const Center(
child: CircularProgressIndicator(color: Colors.cyanAccent),
));
}
}
please refer to this image for clear code sample
Hello #Cheems Monarch
You can try like this :
First create a variable:
int page=1;
Then change your method like this:
void top2Fetch(String page) async {
final top2Scraper = WebScraper('https://mobile.alphacoders.com');
if (await top2Scraper.loadWebPage('/by-category/3?page=$page')) {
top2Wall = top2Scraper.getElement(
'div.container-masonry > div.item > a > img.img-responsive',
['src', 'title']);
// ignore: avoid_print
print(top2Wall);
setState(() {
top2Loaded = true;
});
}
}
And call this function on your button's onPressed:
onPressed: () {
top2Fetch(page.toString());
setState(() {
page++;
});
}
Related
I want to scrap a lazy loaded website using flutter, i used webscraper package as it only scrapes visible elements, how can I scrap all the images and links from the website.
Please refer the image
class Top2 extends StatefulWidget {
const Top2({Key? key}) : super(key: key);
#override
State<Top2> createState() => _Top2State();
}
class _Top2State extends State<Top2> {
late List<Map<String, dynamic>> top2Wall;
bool top2Loaded = false;
void top2Fetch() async {
final top2Scraper = WebScraper('https://mobile.alphacoders.com');
if (await top2Scraper.loadWebPage('/by-category/3?page=1')) {
top2Wall = top2Scraper.getElement(
'div.container-masonry > div.item > a > img.img-responsive',
['src', 'title']);
// ignore: avoid_print
print(top2Wall);
setState(() {
top2Loaded = true;
});
}
}
#override
void initState() {
super.initState();
top2Fetch();
}
#override
Widget build(BuildContext context) {
Size screenSize = MediaQuery.of(context).size;
return Scaffold(
body: top2Loaded
// ignore: sized_box_for_whitespace
? Container(
height: screenSize.height,
width: double.infinity,
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Wrap(children: [
for (int i = 1; i < top2Wall.length; i++)
WallCard(src: top2Wall[i]['attributes']['src'])
]),
),
)
: const Center(
child: CircularProgressIndicator(color: Colors.cyanAccent),
));
}
}
So basically i want to scrap all the wallpaper available from the website but can only scrap the first 24 wallpapers visible.
Its simple.
Rest of the wallpapers are fetched dynamic using JS.
Check Pc Browser > Dev Options> Network and scroll down the page you will se something like:
You can maybe check url where XHR connect and try to scrap it
EDIT
I See page have pagination switch so maybe will be easier to use this
I'm trying to scrape a website using the package web_scraper, where I want the user to click a button and the new link opens where new scrapped images can be shown.
class Top2 extends StatefulWidget {
const Top2({Key? key}) : super(key: key);
#override
State<Top2> createState() => _Top2State();
}
class _Top2State extends State<Top2> {
late List<Map<String, dynamic>> top2Wall;
bool top2Loaded = false;
int page = 2;
void top2Fetch() async {
final top2Scraper = WebScraper('https://mobile.alphacoders.com');
if (await top2Scraper
.loadWebPage('/by-category/3?page=$page&quickload=1')) {
top2Wall = top2Scraper.getElement('div.item > a > img', ['src', 'title']);
// ignore: avoid_print
print(top2Wall);
setState(() {
top2Loaded = true;
});
}
}
#override
void initState() {
super.initState();
top2Fetch();
}
#override
Widget build(BuildContext context) {
Size screenSize = MediaQuery.of(context).size;
return Scaffold(
body: top2Loaded
// ignore: sized_box_for_whitespace
? Container(
height: screenSize.height,
width: double.infinity,
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Wrap(children: [
for (int i = 1; i < top2Wall.length; i++)
WallCard(src: top2Wall[i]['attributes']['src']),
InkWell(
onTap: () {
setState(() {
page++;
// ignore: avoid_print
print(page);
});
},
child: Container(
height: 100,
width: 100,
decoration: const BoxDecoration(color: Colors.cyan)),
)
]),
),
)
: const Center(
child: CircularProgressIndicator(color: Colors.cyanAccent),
));
}[image2][1]
}
For a clear explanation I want to change the page link for scraping, so basically the page number=1 i want to increase it by a number, when user clicks on the container. I used SetState and page++ to increment it by a digit everytime the user clicks on it. I used the print statement to check wether the page increments or not and its sucessfully increases but the page remains same, for clear code view please refer these images enter image description here
enter image description here
If I understand what your issue is, after calling increment page ++ in setstate, you have to call top2Fetch() again inside the onTap method. Like this:
onTap: ()async {
setState(()=>page++);
await top2Fetch();
}
Then update your UI.
You can include a bool value called isLoading in the onTap method: such that when you call the top2Fetch method , a loading spinner is shown on the UI till the future is done.
I try to use lazy load to show the order of the customer by using the ScrollController.
Of course, the new user has a low number of orders and those items are not enough to take up the entire screen. So the ScrollController doesn't work. What I can do?
This code will show a basic lazy load. You can change the _initialItemsLength to a low value like 1 to see this issue.
You can try this at api.flutter.dev
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const Center(
child: MyStatefulWidget(),
),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
late List myList;
ScrollController _scrollController = ScrollController();
int _initialItemsLength = 10, _currentMax = 10;
#override
void initState() {
super.initState();
myList = List.generate(_initialItemsLength, (i) => "Item : ${i + 1}");
_scrollController.addListener(() {
print("scrolling: ${_scrollController.position.pixels}");
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
_getMoreData();
}
});
}
_getMoreData() {
print("load more: ${myList.length}");
for (int i = _currentMax; i < _currentMax + 10; i++) {
myList.add("Item : ${i + 1}");
}
_currentMax = _currentMax + 10;
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
controller: _scrollController,
itemBuilder: (context, i) {
if (i == myList.length) {
return CupertinoActivityIndicator();
}
return ListTile(
title: Text(myList[i]),
);
},
itemCount: myList.length + 1,
),
);
}
}
First, start _initialItemsLength with 10. The scroller will be available and you will see it in the console. After that, change _initialItemsLength to 1. The console will be blank.
scroll listener will be triggered only if user try to scroll
as an option you need to check this condition _scrollController.position.pixels == _scrollController.position.maxScrollExtent after build method executed and each time when user scroll to bottom
just change a bit initState and _getMoreData methods
#override
void initState() {
super.initState();
myList = List.generate(_initialItemsLength, (i) => 'Item : ${i + 1}');
_scrollController.addListener(() => _checkIsMaxScroll());
WidgetsBinding.instance.addPostFrameCallback((_) => _checkIsMaxScroll());
}
void _checkIsMaxScroll() {
if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
_getMoreData();
}
}
_getMoreData() {
print('load more: ${myList.length}');
for (int i = _currentMax; i < _currentMax + 10; i++) {
myList.add('Item : ${i + 1}');
}
_currentMax = _currentMax + 10;
setState(() => WidgetsBinding.instance.addPostFrameCallback((_) => _checkIsMaxScroll()));
}
You can set your ListView with physics: AlwaysScrollableScrollPhysics(), and thus it will be scrollable even when the items are not too many. This will lead the listener to be triggered.
Key code part:
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
physics: AlwaysScrollableScrollPhysics(),
controller: _scrollController,
itemBuilder: (context, i) {
if (i == myList.length) {
return CupertinoActivityIndicator();
}
return ListTile(
title: Text(myList[i]),
);
},
itemCount: myList.length + 1,
),
);
}
The point is 'Find some parameter that can tell whether scroll is enabled or not. If not just load more until the scroll is enabled. Then use a basic step for a lazy load like the code in my question.'
After I find this parameter on google, I don't find this. But I try to check any parameter as possible. _scrollController.any until I found this.
For someone who faces this issue like me.
You can detect the scroll is enabled by using _scrollController.position.maxScrollExtent == 0 with using some delay before that.
This is my code. You can see it works step by step in the console.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class PageStackoverflow72734370 extends StatefulWidget {
const PageStackoverflow72734370({Key? key}) : super(key: key);
#override
State<PageStackoverflow72734370> createState() => _PageStackoverflow72734370State();
}
class _PageStackoverflow72734370State extends State<PageStackoverflow72734370> {
late final List myList;
final ScrollController _scrollController = ScrollController();
final int _initialItemsLength = 1;
bool isScrollEnable = false, isLoading = false;
#override
void initState() {
super.initState();
print("\ninitState work!");
print("_initialItemsLength: $_initialItemsLength");
myList = List.generate(_initialItemsLength, (i) => 'Item : ${i + 1}');
_scrollController.addListener(() {
print("\nListener work!");
print("position: ${_scrollController.position.pixels}");
if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) _getData();
});
_helper();
}
Future _helper() async {
print("\nhelper work!");
while (!isScrollEnable) {
print("\nwhile loop work!");
await Future.delayed(Duration.zero); //Prevent errors from looping quickly.
try {
print("maxScroll: ${_scrollController.position.maxScrollExtent}");
isScrollEnable = 0 != _scrollController.position.maxScrollExtent;
print("isScrollEnable: $isScrollEnable");
if (!isScrollEnable) _getData();
} catch (e) {
print(e);
}
}
print("\nwhile loop break!");
}
void _getData() {
print("\n_getData work!");
if (isLoading) return;
isLoading = true;
int i = myList.length;
int j = myList.length + 1;
for (i; i < j; i++) {
myList.add("Item : ${i + 1}");
}
print("myList.length: ${myList.length}");
isLoading = false;
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
controller: _scrollController,
itemBuilder: (context, i) {
if (i == myList.length) {
return const CupertinoActivityIndicator();
}
return ListTile(title: Text(myList[i]));
},
itemCount: myList.length + 1,
),
);
}
}
You can test in my test. You can change the initial and incremental values at ?initial=10&incremental=1.
I know, this case is rare. Most applications show more data widget height than the height of the screen or the data fetching 2 turns that enough for making these data widget height than the height of the screen. But I put these data widgets in the wrap for users that use the desktop app. So, I need it.
I am building a method that the user can select a prefered profile picture to show arround the app, using provider package. I used shared_preferences to save the profile picture preferences on locally as a int value. And it worked, means I can save the profile picture to local system. But the problem is, the provider package completely became useless in this case, because I have to convert the widget to statefull and call the setState method when ever I insert a profilePicture widget inside the widget tree. And even the profilePicture widget in the HomeScreen not updating this way. I want to know how can I use the provider package for this issue instead of using statefulWidgets.
watch the Gif or video
This is the Provider class I created:
class ProfilePicProvider with ChangeNotifier {
ProfilePicPref profilePicPreferences = ProfilePicPref();
int _svgNumber = 1;
int get svgNumber => _svgNumber;
set svgNumber(int value) {
_svgNumber = value;
profilePicPreferences.setProfilePic(value);
notifyListeners();
}
void changePic(int val) {
_svgNumber = val;
profilePicPreferences.setProfilePic(val);
notifyListeners();
}
}
This is the sharedPreferences class
class ProfilePicPref {
static const PRO_PIC_STS = 'PROFILESTATUS';
setProfilePic(int svgNo) async {
SharedPreferences profilePref = await SharedPreferences.getInstance();
profilePref.setInt(PRO_PIC_STS, svgNo);
}
Future<int> getProfilePicture() async {
SharedPreferences profilePref = await SharedPreferences.getInstance();
return profilePref.getInt(PRO_PIC_STS) ?? 1;
}
}
This is the image selection screen and save that data to sharedPreferences class
class SelectProfilePicture extends StatefulWidget {
const SelectProfilePicture({Key? key}) : super(key: key);
#override
State<SelectProfilePicture> createState() => _SelectProfilePictureState();
}
class _SelectProfilePictureState extends State<SelectProfilePicture> {
int svgNumber = 1;
ProfilePicProvider proProvider = ProfilePicProvider();
#override
void initState() {
getCurrentProfilePicture();
super.initState();
}
void getCurrentProfilePicture() async {
proProvider.svgNumber =
await proProvider.profilePicPreferences.getProfilePicture();
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
CurrentAccountPicture(
path: 'assets/svg/${proProvider.svgNumber}.svg'),
Expanded(
child: GridView.builder(
itemCount: 15,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
),
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
setState(() {
svgNumber = index + 1;
});
proProvider.changePic(index + 1);
proProvider.svgNumber = index + 1;
},
child: SvgPicture.asset('assets/svg/${index + 1}.svg'),
);
},
),
),
],
),
);
}
}
This is the HomeScreen which is not updating the profile image whether it is statefull or stateless
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final proPicProvider = Provider.of<ProfilePicProvider>(context);
return Scaffold(
body:
Column(
children: [
Row(
children: [
CurrentAccountPicture(
path: 'assets/svg/${proPicProvider.svgNumber}.svg'),
],
),
],
),
);
}
}
example:
I have to convert the widget to statefull and call setState method to get the current profile picture from sharedPreferences. You may find this screen from the GIF I provided.
class Progress extends StatefulWidget {
const Progress({Key? key}) : super(key: key);
#override
State<Progress> createState() => _ProgressState();
}
class _ProgressState extends State<Progress> {
ProfilePicProvider proProvider = ProfilePicProvider();
#override
void initState() {
getCurrentProfilePicture();
super.initState();
}
void getCurrentProfilePicture() async {
proProvider.svgNumber =
await proProvider.profilePicPreferences.getProfilePicture();
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SizedBox(
height: 130.0,
width: 130.0,
child: SvgPicture.asset(
'assets/svg/${proProvider.svgNumber}.svg'),
),
),
);
}
}
The problem is in _SelectProfilePictureState when you create new instance of your ChangeNotifier:
ProfilePicProvider proProvider = ProfilePicProvider();. It means you are not using the provider available across the context but creating new one every time. So when the value of your provider changed it has effect only inside _SelectProfilePictureState. Instead of creating new instance you must call it always using the context:
class SelectProfilePicture extends StatefulWidget {
const SelectProfilePicture({Key? key}) : super(key: key);
#override
State<SelectProfilePicture> createState() => _SelectProfilePictureState();
}
class _SelectProfilePictureState extends State<SelectProfilePicture> {
int svgNumber = 1;
// [removed] ProfilePicProvider proProvider = ProfilePicProvider();
//removed
/*void getCurrentProfilePicture() async {
proProvider.svgNumber =
await proProvider.profilePicPreferences.getProfilePicture();
setState(() {});
}*/
#override
Widget build(BuildContext context) {
//use provider from the context
final proProvider = Provider.of<ProfilePicProvider>(context,listen:true);
return Scaffold(
body: Column(
children: [
CurrentAccountPicture(
path: 'assets/svg/${proProvider.svgNumber}.svg'),
Expanded(
child: GridView.builder(
itemCount: 15,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
),
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
setState(() {
svgNumber = index + 1;
});
proProvider.changePic(index + 1);
proProvider.svgNumber = index + 1;
},
child: SvgPicture.asset('assets/svg/${index + 1}.svg'),
);
},
),
),
],
),
);
}
}
If you enter the application you may want send initially selected image to your provider:
Add parameter to the constructor of ProfilePicProvider:
ProfilePicProvider(SharedPreferences prefs): _svgNumber = prefs.getInt(ProfilePicPref.PRO_PIC_STS) ?? 1;
In main.dart:
Future<void> main()async{
WidgetsFlutterBinding.ensureInitialized();
var prefs = await SharedPreferences.getInstance();
runApp(
MultiProvider(
providers:[
ChangeNotifierProvider( create:(_) => ProfilePicProvider(prefs))
],
child: yourtopWidget
)
);
}
I'm writing a language learning app and I'm stuck. What am I trying to do is when user pressed the next button, I want to increase the index and show other page in lesson.dart. I have many pages like listening, video etc.
And I want to call nextPage() without initialize Lesson class.
create_word.dart
class CreateWord extends StatefulWidget {
var pathToPlay = '';
String word = '';
String sentence = '';
CreateWord(this.pathToPlay, this.word, this.sentence);
#override
_CreateWordState createState() => _CreateWordState();
}
class _CreateWordState extends State<CreateWord> {
late AudioPlayer player;
#override
void initState() {
super.initState();
player = AudioPlayer();
}
#override
void dispose() {
player.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
Row(
children: [
// When pressed this button, call nextPage() in lesson.dart
ElevatedButton(
child: Text("Play Sound", style: TextStyle(color: Colors.white, fontSize: 13),),
onPressed: () async{
await player.setAsset(widget.pathToPlay);
player.play();
},
), // The Button
Text(widget.word),
Text(widget.sentence)
],
),
],
),
);
}
}
lesson.dart
class Lesson extends StatefulWidget {
int lesson_index = 0;
Lesson(this.lesson_index);
#override
LessonState createState() => LessonState();
}
class LessonState extends State<Lesson> {
final lessonKey = GlobalKey<LessonState>();
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xff413250),
appBar: buildAppBar(),
body: buildBody(),
//bottomNavigationBar: buildNavbar(),
);
}
late int _lesson_index = widget.lesson_index;
int _page_index = 0;
Widget setLesson(){
var page = data[_lesson_index]['pages'][_page_index];
//switch("video"){
switch(data.isNotEmpty ? page['type'] : null){
case "text":
return Text("Text");
case "video":
return CreateVideo(page['path']);
case "word":
return CreateWord(page['path'], page['word'], page['sentence']);
case "audio_match":
return CreateAudioMatch(page['answers'], page['text'], page['correct_answer_index'], page['complete']);
case "complete_text":
return CreateCompleteText(page['text'], page['answer'], page['complete']);
default:
return Text("Bir hata oluştu. " + page.toString());
}
}
// Call this when button pressed in
void nextPage(){
setState(() {
_page_index < data[_lesson_index]['pages'].length - 1 ? _page_index++ : null;
});
}
}
You can do you function static
static nextPage...
and then get it from anywhere, like this:
Lesson.nextPage