flutter infinite scrolling data from server in listview builder - flutter

I am using graphql_flutter to load data from the server and I should update moreId for the update page in my server and get more data to load, and I need to use infinite for it.
How can I do it?
class MoreEpd extends StatefulWidget {
final String moreId;
const MoreEpd({Key? key, required this.moreId}) : super(key: key);
#override
_MoreEpdState createState() => _MoreEpdState();
}
class _MoreEpdState extends State<MoreEpd> {
double pageWidth = 0;
double pageHeigh = 0;
int pageNum = 0;
final String leftArrow = 'assets/icons/left-arrow.svg';
String getSearchResult = """
query homeview(\$moreId: ID!, \$page: Int! ){
homeview(HM_ID: \$moreId, page: \$page){
HM_ID
HM_Type_ID
HM_Type_Name
HM_NAME
Priority
Details{
HM_Det_ID
HM_ID
Ep_ID
Pod_ID
Link
Image
title
Pod_title
}
}
}
""";
#override
Widget build(BuildContext context) {
pageWidth = MediaQuery.of(context).size.width;
pageHeigh = MediaQuery.of(context).size.height;
return Container(
child: Query(
options: QueryOptions(
document: gql(getSearchResult),
variables: {'moreId': widget.moreId, 'page': pageNum},
),
builder: (
QueryResult result, {
Refetch? refetch,
FetchMore? fetchMore,
}) {
return handleResult(result);
},
),
);
}
Widget handleResult(QueryResult result) {
var data = result.data!['homeview']['Details'] ?? [];
return Container(
child: ListView.builder(
padding: EdgeInsets.only(top: 15),
shrinkWrap: true,
itemCount: data.length ,
itemBuilder: (context, index) {
return InkWell(
onTap: () {},
child: Padding(
padding: EdgeInsets.only(
top: pageWidth * 0.0,
right: pageWidth * 0.08,
left: pageWidth * 0.08,
bottom: pageWidth * 0.0),
child: Container(
child: Stack(
children: [
Column(
children: [
Padding(
padding:
EdgeInsets.only(bottom: pageWidth * 0.060),
child: Row(
children: [
Padding(
padding:
EdgeInsets.only(left: pageWidth * 0.01),
child: Container(
// alignment: Alignment.centerRight,
width: pageWidth * 0.128,
height: pageWidth * 0.128,
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: CachedNetworkImageProvider(
data[index]['Image'],
)),
borderRadius: BorderRadius.all(
Radius.circular(15)),
// color: Colors.redAccent,
border: Border.all(
color: MyColors.lightGrey,
width: 1,
)),
),
),
Expanded(
child: Row(
children: [
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Container(
width: pageWidth * 0.5,
alignment: Alignment.centerRight,
child: Text(
data[index]['title'],
textAlign: TextAlign.right,
overflow: TextOverflow.ellipsis,
maxLines: 1,
// softWrap: true,
style: TextStyle(
// fontWeight: FontWeight.bold,
fontSize: 14,
),
),
),
],
),
],
),
)
],
),
),
],
),
],
),
),
),
);
}));
}
}

First error is happening because of not handling the states of Query. In order to do that on builder:
delearing data on state level var data = [];
builder: (
QueryResult result, {
Refetch? refetch,
FetchMore? fetchMore,
}) {
if (result.hasException) {
return Text(result.exception.toString());
}
if (result.isLoading) {
return Column(
children: [
Expanded(
child: handleResult(data)), // show data while loading
const Center(
child: CircularProgressIndicator(),
),
],
);
}
data.addAll(result.data!['homeview']['Details'] ?? []);
return handleResult(data);
},
All we need now to increase the pageNum to get more data. I'm using load more button, better will be creating the load button end of list by increasing itemLength+=1.
Update using ScrollController.
// on state class
final ScrollController controller = ScrollController();
bool isLoading = false;
Load data on scroll
#override
void initState() {
super.initState();
controller.addListener(() {
/// load date at when scroll reached -100
if (controller.position.pixels >
controller.position.maxScrollExtent - 100) {
print("Scroll on loading");
if (!isLoading) {
print("fetching");
setState(() {
pageNum++;
isLoading = true;
});
}
}
});
}
Full Snippet on Gist
And about the position issue, you can check this

Related

Pull to refresh in Flutter doesn't invoke onLoading function unless I switch page and come back

I am using a smart refresher for pagination in Flutter. It calls the below function to get data from the API on the basis of start and end date.
Whenever the smart refresher calls this function, it gives the right data the first time (with initial start and end date), but when I change the dates, it fetches new data accordingly but now when I pull it again to fetch more data, it doesn't even recognise that pull action as a valid action and nothing happens.
Unless I switch to another screen and come back, it works as expected.
If you need more info or code segments, I'll gladly provide them.
// THIS IS THE FUNCTION CALLED IN SMART REFRESHER in init() isFirst = true otherwise its false
Future<void> fetchAnalytics({required bool isFirst}) async {
if (isFirst == true) {
analyticsLoadingFirst(true);
analyticsPage.value = 1;
analyticsTotalPage.value = 0;
analyticsList.clear();
} else {
analyticsLoading(true);
}
analytics.value = await RemoteServices.getStatsAnalytics(
token: Constants.token!,
start: DateFormat('yyyy-MM-dd'). format(analyticsStartDate.value),
end: DateFormat('yyyy-MM-dd'). format(analytiscEndDate.value),
page: analyticsPage.value,
);
if (analytics.value.data!.data!.isNotEmpty) {
analyticsList.value = analyticsList.value + analytics.value.data!.data!;
analyticsPage.value += 1;
analyticsTotalPage.value = analytics.value.data!.lastPage!;
}
isFirst == true ? analyticsLoadingFirst(false) : analyticsLoading(false);
}
// THIS IS THE TAB
class AnalyticsStatsScreen extends StatefulWidget {
const AnalyticsStatsScreen({Key? key}) : super(key: key);
#override
State<AnalyticsStatsScreen> createState() => _AnalyticsStatsScreenState();
}
class _AnalyticsStatsScreenState extends State<AnalyticsStatsScreen> with TickerProviderStateMixin {
final statsController = Get.find<StatsController>();
late AnimationController animationController;
late AnimationController animationController2;
RefreshController paginateController = RefreshController();
ScrollController? _scroll;
#override
void initState() {
super.initState();
_scroll = ScrollController();
animationController = AnimationController(vsync: this);
animationController2 = AnimationController(vsync: this);
}
#override
void setState(VoidCallback fn) {
if (mounted) super.setState(fn);
}
#override
void dispose() {
animationController.dispose();
animationController2.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Obx(
() => statsController.analyticsLoadingFirst.isTrue
? Center(
child: AlertWidgets.IULoading(animationController1: animationController, animationController2: animationController2),
)
: statsController.analytics.value.data == null
? LoadingError().notFoundWidget()
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 12.h),
/// ****** Today Calendar Button ******
GestureDetector(
onTap: () {
Get.defaultDialog(
title: "Select a range",
titlePadding: EdgeInsets.only(top: 16.h),
titleStyle: TextStyle(fontSize: 18.sp),
contentPadding: EdgeInsets.zero,
content: SizedBox(
// width: 0.5.sw,
// height: 0.65.sw,
width: 0.75.sw,
height: 0.75.sw,
child: SfDateRangePicker(
onSelectionChanged: _onChange,
onSubmit: (value) {
Get.back();
statsController.analyticsPage.value = 1;
statsController.analyticsTotalPage.value = 0;
_scrollUp();
statsController.fetchAnalytics(isFirst: true);
},
onCancel: () {
Get.back();
},
headerStyle: DateRangePickerHeaderStyle(
textStyle: ThemeStyles.NormalLight,
),
selectionMode: DateRangePickerSelectionMode.range,
maxDate: DateTime.now(),
showActionButtons: true,
headerHeight: 50.h,
rangeTextStyle: ThemeStyles.NormalLight,
selectionTextStyle: ThemeStyles.NormalLight.copyWith(color: Constants.TextWhite),
initialSelectedRange: PickerDateRange(statsController.startDate.value, statsController.endDate.value
// DateTime.now(),
// DateTime.now()
// .subtract(Duration(days: 7)),
),
),
),
);
},
child: Container(
width: Get.width,
height: 0.06.sh,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4.r),
border: Border.all(color: Constants.TextBlack),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
Constants.calender,
width: 22.w,
height: 22.w,
),
SizedBox(width: 12.w),
Text(
'Today',
style: ThemeStyles.NormalBold,
),
],
),
),
),
Expanded(
child: ExcludeSemantics(
child: Obx(
() => SmartRefresher(
// scrollController: _scroll,
controller: paginateController,
enablePullUp: true,
enablePullDown: false,
onLoading: () async {
print("total page loading => ${statsController.analyticsTotalPage.value}");
print("current page loading => ${statsController.analyticsPage.value}");
if (statsController.analyticsTotalPage.value >= statsController.analyticsPage.value) {
await statsController.fetchAnalytics(isFirst: false);
if (statsController.analyticsLoading.isFalse) {
paginateController.loadComplete();
} else {
paginateController.loadFailed();
}
} else {
paginateController.loadNoData();
}
},
child: ListView.builder(
controller: _scroll,
shrinkWrap: true,
padding: EdgeInsets.zero,
physics: const BouncingScrollPhysics(),
itemCount: statsController.analyticsList.length,
itemBuilder: (ctx, index) {
// int count = index + 1;
var item = statsController.analyticsList[index];
return productCard(analyticsDatum: item, index: index);
},
),
),
),
),
),
SizedBox(height: 12.h),
],
),
);
}
// ***** Paid Summary *****
Widget productCard({required AnalyticsDatum analyticsDatum, int? index}) {
// double netSales = saleDatum.amount! - (saleDatum.discount! + saleDatum.tax!) - saleDatum.refundAmount!;
return Padding(
padding: EdgeInsets.only(top: 12.h),
child: Container(
width: Get.width,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.r),
color: Constants.BackgroundSecondary,
),
padding: EdgeInsets.symmetric(vertical: 12.h, horizontal: 8.w),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
SizedBox(
width: Get.width > 600 ? 10.w : 16.w,
height: Get.width > 600 ? 15.h : 16.h,
child: SvgPicture.asset(
Constants.analytic,
// color: Colors.black,
// size: 30.w,
),
),
SizedBox(width: 8.w),
Flexible(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: Get.width * 0.55,
child: Text(
analyticsDatum.productTitle.toString(),
style: ThemeStyles.NormalLightW600,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
Text(
"${analyticsDatum.counts} Views",
style: ThemeStyles.NormalLightW600,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
],
),
),
);
}
void _onChange(DateRangePickerSelectionChangedArgs args) {
print("DateRangePickerSelectionChangedArgs = ${args.value.startDate}");
if (args.value is PickerDateRange) {
statsController.analyticsStartDate.value = args.value.startDate;
statsController.analytiscEndDate.value = args.value.endDate ?? args.value.startDate;
}
}
void _scrollUp() {
if (_scroll!.hasClients){
_scroll!.jumpTo(_scroll!.position.minScrollExtent);
}
}
}
// THIS IS THE CONTROLLER BEING FOUND AT THE START OF THE TAB
class StatsController extends GetxController {
var startDate = DateTime.now().subtract(Duration(days: 6)).obs;
var endDate = DateTime.now().obs;
var analyticsStartDate = DateTime.now().subtract(Duration(days: 6)).obs;
var analytiscEndDate = DateTime.now().obs;
var analyticsLoadingFirst = false.obs;
var analyticsLoading = false.obs;
var analytics = AnalyticsModel().obs;
var analyticsList = <AnalyticsDatum>[].obs;
// ******** Analytics *******
Future<void> fetchAnalytics({required bool isFirst}) async {
if (isFirst == true) {
analyticsLoadingFirst(true);
analyticsPage.value = 1;
analyticsTotalPage.value = 0;
analyticsList.clear();
} else {
analyticsLoading(true);
}
analytics.value = await RemoteServices.getStatsAnalytics(
token: Constants.token!,
start: DateFormat('yyyy-MM-dd').format(analyticsStartDate.value),
end: DateFormat('yyyy-MM-dd').format(analytiscEndDate.value),
page: analyticsPage.value,
);
if (analytics.value.data!.data!.isNotEmpty) {
analyticsList.value = analyticsList.value + analytics.value.data!.data!;
analyticsPage.value += 1;
analyticsTotalPage.value = analytics.value.data!.lastPage!;
}
isFirst == true ? analyticsLoadingFirst(false) : analyticsLoading(false);
}
#override
void onInit() {
fetchAnalytics(isFirst: true);
super.onInit();
}
#override
void onClose() {
startDate.value = DateTime.now().subtract(Duration(days: 7));
endDate.value = DateTime.now();
super.onClose();
}
}
I've tried to look it up in the docs of pull to refresh but found nothing
You may have loaded all the data and called the refreshController.loadNoData() function.
Once you call the refreshController.loadNoData() then onLoading function will not be invoked by the time you call the refreshController.resetNoData().
In short, to get rid of this issue just call the refreshController.resetNoData() in onRefresh function.

Flutter - Image.memory not refreshing after source change

I have a page that allows users to upload documents (as images). I have structured my page in a way that for each document type that can be uploaded a Document_Upload widget is used to reduce the amount of repeated code.
On initial load I use a FutureBuilder to get all the documents the user has already uploaded from our REST Api and then populate each Document_Upload widget with the relevant data.
On successful upload our REST Api returns the new image back to the Flutter app as a Byte Array so it can be displayed.
The problem I am currently facing is that no matter what I try the image widget (Image.memory) does not display the new image, it just stays on the old one.
I have tried almost everything I can think of/ find online to resolve this issue, including:
Calling setState({}); after updating the imageString variable - I can see the widget flash but it remains on the original image.
Using a function to callback to the parent widget to rebuild the entire child widget tree - same result as setState, all the widgets flash, but no update.
Calling imageCache.clear() & imageCache.clearLiveImages() before updating the imageString.
Using CircleAvatar instead of Image.memory.
Rebuilding the Image widget by calling new Image.memory() inside the setState call.
I am starting to question if this is an issue related to Image.memory itself, however, using Image.File / Image.network is not an option with our current requirement.
Refreshing the page manually causes the new image to show up.
My code is as follows:
documents_page.dart
class DocumentsPage extends StatefulWidget {
#override
_DocumentsPageState createState() => _DocumentsPageState();
}
class _DocumentsPageState extends State<DocumentsPage>
with SingleTickerProviderStateMixin {
Future<Personal> _getUserDocuments;
Personal _documents;
#override
void didChangeDependencies() {
super.didChangeDependencies();
_getUserDocuments = sl<AccountProvider>().getUserDocuments();
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: SafeArea(
child: Center(
child: Padding(
padding: EdgeInsets.all(20),
child: Container(
constraints: BoxConstraints(maxWidth: 1300),
child: buildFutureBuilder(context)),
)),
),
);
}
Widget buildFutureBuilder(BuildContext context) {
var screenSize = MediaQuery.of(context).size;
return FutureBuilder<Personal>(
future: _getUserDocuments,
builder: (context, AsyncSnapshot<Personal> snapshot) {
if (!snapshot.hasData) {
return Text("Loading");
} else {
if (snapshot.data == null) {
return Center(child: Text('Error: ${snapshot.error}'));
} else {
_documents = snapshot.data;
return Column(
children: [
SizedBox(height: 20.0),
Text(
"DOCUMENTS",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.bold,
color: AppColors.navy),
),
Container(
constraints: BoxConstraints(maxWidth: 250),
child: Divider(
color: AppColors.darkBlue,
height: 20,
),
),
Container(
margin: EdgeInsets.only(top: 5.0, bottom: 5.0),
child: Text(
"These documents are required in order to verify you as a user",
style: TextStyle(fontSize: 14))),
Container(
margin: EdgeInsets.only(bottom: 25.0),
child: Text("View our Privacy Policy",
style: TextStyle(fontSize: 14))),
Container(
child: screenSize.width < 768
? Column(
children: [
DocumentUpload(
imageType: "ID",
imageString: _documents.id),
DocumentUpload(
imageType: "Drivers License Front",
imageString: _documents.driversLicenseFront,
),
DocumentUpload(
imageType: "Drivers License Back",
imageString: _documents.driversLicenseBack,
)
],
)
: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
DocumentUpload(
imageType: "ID",
imageString: _documents.id),
DocumentUpload(
imageType: "Drivers License Front",
imageString: _documents.driversLicenseFront,
),
DocumentUpload(
imageType: "Drivers License Back",
imageString: _documents.driversLicenseBack,
),
])),
Container(
child: screenSize.width < 768
? Container()
: Padding(
padding:
EdgeInsets.only(top: 10.0, bottom: 10.0))),
Container(
child: screenSize.width < 768
? Column(
children: [
DocumentUpload(
imageType: "Selfie",
imageString: _documents.selfie,
),
DocumentUpload(
imageType: "Proof of Residence",
imageString: _documents.proofOfResidence,
),
Container(width: 325)
],
)
: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
DocumentUpload(
imageType: "Selfie",
imageString: _documents.selfie,
),
DocumentUpload(
imageType: "Proof of Residence",
imageString: _documents.proofOfResidence,
),
Container(width: 325)
])),
],
);
}
}
});
}
}
document_upload.dart
class DocumentUpload extends StatefulWidget {
final String imageType;
final String imageString;
const DocumentUpload({this.imageType, this.imageString});
#override
_DocumentUploadState createState() => _DocumentUploadState();
}
class _DocumentUploadState extends State<DocumentUpload> {
String _imageType;
String _imageString;
bool uploadPressed = false;
Image _imageWidget;
#override
Widget build(BuildContext context) {
setState(() {
_imageType = widget.imageType;
_imageString = widget.imageString;
_imageWidget =
new Image.memory(base64Decode(_imageString), fit: BoxFit.fill);
});
return Container(
constraints: BoxConstraints(maxWidth: 325),
height: 200,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
boxShadow: [
new BoxShadow(
color: AppColors.lightGrey,
blurRadius: 5.0,
offset: Offset(0.0, 3.0),
),
],
),
child: Card(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
child: Column(children: <Widget>[
Padding(padding: EdgeInsets.only(top: 5.0)),
Row(
//ROW 1
children: <Widget>[
Expanded(
child: Text(
_imageType,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppColors.darkBlue),
),
),
],
),
Row(
//ROW 2
children: <Widget>[
Expanded(
child: Container(
padding: EdgeInsets.only(left: 5.0, bottom: 5.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(20.0),
child: _imageWidget,
)),
),
Consumer<AccountProvider>(
builder: (context, provider, child) {
return Padding(
padding: EdgeInsets.all(10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Padding(
padding:
EdgeInsets.only(top: 5.0, bottom: 5.0),
child: Icon(Icons.star,
size: 20, color: AppColors.darkBlue)),
Padding(
padding:
EdgeInsets.only(top: 5.0, bottom: 5.0),
child: Text('Drag file here or',
textAlign: TextAlign.center)),
Padding(
padding:
EdgeInsets.only(top: 5.0, bottom: 5.0),
child: DynamicGreyButton(
title: uploadPressed
? "Uploading ..."
: "Browse",
onPressed: () async {
FilePickerResult result =
await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: [
'jpg',
'jpeg',
'png'
]);
if (result != null) {
uploadPressed = true;
Uint8List file =
result.files.single.bytes;
String fileType =
result.files.single.extension;
await provider
.doUploadDocument(
_imageType, file, fileType)
.then((uploadResult) {
if (uploadResult == null ||
uploadResult == '') {
showToast(
"Document failed to upload");
return;
} else {
showToast("Document uploaded",
Colors.green, "#66BB6A");
uploadPressed = false;
_imageString = uploadResult;
setState(() {});
}
});
} else {
// User canceled the picker
uploadPressed = false;
}
},
))
]));
})
],
),
])));
}
}
Image Upload HTTP Call
#override
Future uploadDocuments(DocumentsUpload model) async {
final response = await client.post(
Uri.https(appConfig.baseUrl, "/api/Account/PostDocuments_Flutter"),
body: jsonEncode(model.toJson()),
headers: <String, String>{
'Content-Type': 'application/json'
});
if (response.statusCode == 200) {
var data = json.decode(response.body);
return data;
} else {
return "";
}
}
EDIT: Attached GIF of current behaviour.
I am pretty much out of ideas at this point, any help would be greatly appreciated.
Came up with a solution.
I created a second variable to hold the new image string and showed an entirely new image widget once the second variable had value.
String _newImage;
In the success of the upload...
_newImage = uploadResult;
setState(() {});
Image widget...
child: (_newImage == null || _newImage == '')
? new Image.memory(base64Decode(_imageString), fit: BoxFit.fill)
: new Image.memory(base64Decode(_newImage), fit: BoxFit.fill)
Not a very elegant solution, but it's a solution, but also not necessarily the answer as to why the original issue was there.

Flutter Slide Transition To a Specific Location

I'm making a grammar quiz app using flutter, I have a question and a couple of choices, I want to make the choice slides to the empty space part of the question with a slide animation
For example:
How is _ new School?
(You) (Your) (It)
and when I press on (Your) the choice widget slides to the _ leaving an empty container
How is (Your) new School?
(You) ( ) (It)
I Made it with Draggable and DragTarget and you can see it in these images
image 1
image 2
but I want it to slide when I press on it without dragging and dropping
here is some of the code
class QuestionsScreen extends StatefulWidget {
QuestionsScreen({Key key}) : super(key: key);
#override
_QuestionsScreenState createState() => _QuestionsScreenState();
}
class _QuestionsScreenState extends State<QuestionsScreen> {
String userAnswer = "_";
int indexOfDragPlace = QuestionBrain.getQuesitonText().indexOf("_");
#override
Widget build(BuildContext context) {
final screenSize = MediaQuery.of(context).size;
return Scaffold(
body: SafeArea(
child: Column(
children: [
Container(
padding: EdgeInsets.all(10),
color: Colors.white,
child: Center(
child: Scrollbar(
child: ListView(
children: [
Center(
child: Wrap(
children: [
...QuestionBrain.getQuesitonText()
.substring(0, indexOfDragPlace)
.split(" ")
.map((e) => QuestionHolder(
question: e + " ",
)),
_buildDragTarget(),
...QuestionBrain.getQuesitonText()
.substring(indexOfDragPlace + 1)
.split(" ")
.map((e) => QuestionHolder(
question: e + " ",
)),
],
),
)
],
),
),
),
),
Wrap(
children: [
...QuestionBrain.choices.map((choice) {
if (choice == userAnswer) {
return ChoiceHolder(
choice: "",
backGroundColor: Colors.black12,
);
}
return DraggableChoiceBox(
choice: choice,
userAnswer: userAnswer,
onDragStarted: () {
setState(() {
dragedAnswerResult = "";
});
},
onDragCompleted: () {
setState(() {
userAnswer = choice;
setState(() {
answerColor = Colors.orange;
});
print("Called");
});
},
);
}).toList()
],
),
],
),
),
);
}
Widget _buildDragTarget() {
return DragTarget<String>(
builder: (context, icoming, rejected) {
return Material(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
width: MediaQuery.of(context).size.width * 0.20,
height: MediaQuery.of(context).size.height * 0.05,
color: answerColor,
child: FittedBox(
child: Text(
userAnswer,
style: TextStyle(
fontSize: 12,
color: Colors.white,
fontWeight: FontWeight.bold),
),
),
),
);
},
onAccept: (data) {
userAnswer = data;
answerColor = Colors.orange;
},
);
}
}
class DraggableChoiceBox extends StatelessWidget {
const DraggableChoiceBox({
Key key,
this.choice,
this.userAnswer,
this.onDragCompleted,
this.onDragStarted,
}) : super(key: key);
final String choice;
final String userAnswer;
final Function onDragCompleted;
final Function onDragStarted;
#override
Widget build(BuildContext context) {
return Draggable(
onDragCompleted: onDragCompleted,
data: choice,
child: ChoiceHolder(choice: choice),
feedback: Material(
elevation: 20,
child: ChoiceHolder(
choice: choice,
margin: 0,
),
),
childWhenDragging: ChoiceHolder(
choice: "",
backGroundColor: Colors.black12,
),
onDragStarted: onDragStarted,
);
}
}
You can use overlays similar to the way Hero widgets work, here is an "unpolished" example:
import 'package:flutter/material.dart';
class SlideToPosition extends StatefulWidget {
#override
_SlideToPositionState createState() => _SlideToPositionState();
}
class _SlideToPositionState extends State<SlideToPosition> {
GlobalKey target = GlobalKey();
GlobalKey toMove = GlobalKey();
double dx = 0.0, dy = 0.0, dxStart = 0.0, dyStart = 0.0;
String choosedAnswer = '', answer = 'answer', finalAnswer = '';
OverlayEntry overlayEntry;
#override
void initState() {
overlayEntry = OverlayEntry(
builder: (context) => TweenAnimationBuilder(
duration: Duration(milliseconds: 500),
tween:
Tween<Offset>(begin: Offset(dxStart, dyStart), end: Offset(dx, dy)),
builder: (context, offset, widget) {
return Positioned(
child: Material(
child: Container(
color: Colors.transparent,
height: 29,
width: 100,
child: Center(child: Text(choosedAnswer)))),
left: offset.dx,
top: offset.dy,
);
},
),
);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
SizedBox(
height: 20,
),
Row(
children: [
Text('text'),
Container(
key: target,
height: 30,
width: 100,
child: Center(child: Text(finalAnswer)),
decoration:
BoxDecoration(border: Border(bottom: BorderSide())),
),
Text('text')
],
),
SizedBox(
height: 20,
),
GestureDetector(
child: Container(
height: 30,
width: 100,
color: Colors.blue[200],
child: Center(child: Text(answer, key: toMove))),
onTap: () async {
setState(() {
answer = '';
});
RenderBox box1 = target.currentContext.findRenderObject();
Offset targetPosition = box1.localToGlobal(Offset.zero);
RenderBox box2 = toMove.currentContext.findRenderObject();
Offset toMovePosition = box2.localToGlobal(Offset.zero);
setState(() {
answer = '';
choosedAnswer = 'answer';
});
dxStart = toMovePosition.dx;
dyStart = toMovePosition.dy;
dx = targetPosition.dx;
dy = targetPosition.dy;
Overlay.of(context).insert(overlayEntry);
setState(() {});
await Future.delayed(Duration(milliseconds: 500));
overlayEntry.remove();
setState(() {
finalAnswer = 'answer';
});
},
),
],
),
),
);
}
}
Sorry for the poor naming of the variables :)

How to show all dates in a horizontal scroll view

I want to have all the dates of a month in a horizontal scroll view. The current week 7 days should be displayed first and on scrolling right the previous dates should be shown. later on scrolling left the later weeks dates should be displayed an don tap of a date i should get the date in return. How to do this? I have tried using the below. It displays dates and scrolls horizontally as well but it displays only multiple of 7 and all the exact dates of a month. Also on tapping the date it does not return the position form listview builder as 0 and i returns the index.
Widget displaydates(int week) {
return ListView.builder(
itemCount: 5,
itemBuilder: (BuildContext context, int position) {
return Row(
children: <Widget>[
for (int i = 1; i < 8; i++)
Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onTap: () {
print(position);
},
child: Text(
((week * 7) + i).toString() + " ",
style: TextStyle(),
),
),
),
],
);
});
}
I am calling this like:
displaydates(0),
displaydates(1),
displaydates(2),
displaydates(3),
displaydates(4),
UPDATE:
You can get last day of month using below code :
DateTime(year,month + 1).subtract(Duration(days: 1)).day;
You should use ModalBottomSheet for the same and then pop that on selection with the Navigator.of(context).pop(result);
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return ListView.builder(
itemCount: 5,
itemBuilder: (BuildContext context, int position) {
return Row(
children: <Widget>[
for (int i = 1; i < 8; i++)
Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onTap: () {
Navigator.of(context).pop(position);
},
child: Text(
((week * 7) + i).toString() + " ",
style: TextStyle(),
),
),
),
],
);
}
);
}
);
I have created a widget that let me select date from a list and it is scrollable(along time ago). There lot of code but you can use the selected date under any widget which parent wrapped from InheritedWidget.
Here is the code(Note that I also created a package for this, if you dont like to write this much code for this):
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class MyInheritedWidget extends InheritedWidget {
final DateTime date;
final int selectedDay;
final int monthDateCount;
final bool isDateHolderActive;
final Map<int, bool> dayAvailabilityMap;
final ValueChanged<bool> toggleDateHolderActive;
final ValueChanged<int> setSelectedDay;
MyInheritedWidget({
Key key,
this.date,
this.selectedDay,
this.monthDateCount,
this.isDateHolderActive,
this.dayAvailabilityMap,
this.toggleDateHolderActive,
this.setSelectedDay,
Widget child,
}) : super(key: key, child: child);
#override
bool updateShouldNotify(MyInheritedWidget oldWidget) {
return oldWidget.selectedDay != selectedDay ||
oldWidget.toggleDateHolderActive != toggleDateHolderActive;
}
}
class DateIndicatorPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
DateIndicator(),
Expanded(
child: Container(),
),
],
),
),
);
}
}
class DateIndicator extends StatefulWidget {
static MyInheritedWidget of(BuildContext context) => context.dependOnInheritedWidgetOfExactType();
#override
_DateIndicatorState createState() => _DateIndicatorState();
}
class _DateIndicatorState extends State<DateIndicator> {
DateTime date = DateTime.now();
int selectedDay = 1;
int monthDateCount = 1;
bool isDateHolderActive = false;
Map<int, bool> dayAvailabilityMap = {};
void toggleDateHolderActive(bool flag) {
setState(() {
isDateHolderActive = flag;
});
}
void setSelectedDay(int index) {
setState(() {
selectedDay = index;
});
}
#override
void initState() {
final DateTime dateForValues = new DateTime(date.year, date.month + 1, 0);
monthDateCount = dateForValues.day;
// Just to show how to activate when something exist for this day(from network response or something)
dayAvailabilityMap[1] = true;
dayAvailabilityMap[2] = true;
dayAvailabilityMap[3] = true;
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
height: 68.0,
padding:
const EdgeInsets.only(left: 7.0, right: 3.0, top: 2.0, bottom: 2.0),
decoration: BoxDecoration(
color: Theme.of(context).secondaryHeaderColor,
boxShadow: [
BoxShadow(
color: Colors.blueAccent.withOpacity(.7),
offset: Offset(0.0, .5),
blurRadius: 3.0,
spreadRadius: 0.3),
],
),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: monthDateCount, // to avoid showing zero
itemBuilder: (BuildContext context, int index) {
return MyInheritedWidget(
date: date,
selectedDay: selectedDay,
monthDateCount: monthDateCount,
isDateHolderActive: isDateHolderActive,
dayAvailabilityMap: dayAvailabilityMap,
toggleDateHolderActive: toggleDateHolderActive,
setSelectedDay: setSelectedDay,
child: DateHolder(index));
}),
);
}
}
class DateHolder extends StatelessWidget {
DateHolder(this.index);
final int index;
final Widget activeBubble = Container(
width: 15.0,
height: 15.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.deepOrangeAccent,
),
);
#override
Widget build(BuildContext context) {
final appState = DateIndicator.of(context);
return InkWell(
onTap: () {
appState.toggleDateHolderActive(true);
appState.setSelectedDay(index);
print("Date ${index} selected!");
},
child: Stack(
children: <Widget>[
Column(
children: <Widget>[
Container(
margin: const EdgeInsets.only(right: 5.0),
child: Text(
"${DateFormat('EEEE').format(DateTime(appState.date.year, appState.date.month, index)).substring(0, 1)}",
style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 12.0),
)),
Container(
width: 45.0,
height: 45.0,
margin: const EdgeInsets.only(right: 5.0),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
border: (index == (appState.selectedDay) &&
appState.isDateHolderActive == true)
? Border.all(width: 2.0, color: Theme.of(context).primaryColor)
: Border.all(color: Colors.transparent),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Text(
"${index + 1}", // to avoid showing zero
style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 16.0),
),
),
),
),
],
),
(appState.dayAvailabilityMap[index] ?? false)
? Positioned(right: 8.0, bottom: 5.0, child: activeBubble)
: Container(),
],
),
);
}
}

Adding different onTap pages for seperate cards

I was looking at a UI which have different cards placed vertically, I tried adding onTap feature to open a separate page for different cards each.In this UI there's vertical cards placed with the data of images,title and a read later button,Adding a gesture detector above clipReact to add the onTap material page route feature and adding onTap below the read later button but where should I place the onTap
onTap: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (_) => /*what to define here*/)),
Is there a way to add separate onTap feature for separate page for different cards ?
class CardScrollWidget extends StatelessWidget {
var currentPage;
var padding = 20.0;
var verticalInset = 20.0;
CardScrollWidget(this.currentPage);
#override
Widget build(BuildContext context) {
return new AspectRatio(
aspectRatio: widgetAspectRatio,
child: LayoutBuilder(builder: (context, contraints,) {
var width = contraints.maxWidth;
var height = contraints.maxHeight;
var safeWidth = width - 2 * padding;
var safeHeight = height - 2 * padding;
var heightOfPrimaryCard = safeHeight;
var widthOfPrimaryCard = heightOfPrimaryCard * cardAspectRatio;
var primaryCardLeft = safeWidth - widthOfPrimaryCard;
var horizontalInset = primaryCardLeft / 2;
List<Widget> cardList = new List();
for (var i = 0; i < images.length; i++) {
var delta = i - currentPage;
bool isOnRight = delta > 0;
var start = padding + max(primaryCardLeft -horizontalInset * -delta * (isOnRight ? 15 : 1),
0.0);
var cardItem = Positioned.directional(
top: padding + verticalInset * max(-delta, 0.0),
bottom: padding + verticalInset * max(-delta, 0.0),
start: start,
textDirection: TextDirection.rtl,
child: GestureDetector(
child: ClipRRect(
borderRadius: BorderRadius.circular(16.0),
child:Container(
decoration: BoxDecoration(color: Colors.white, boxShadow: [
BoxShadow(
color: Colors.black12,
offset: Offset(3.0, 6.0),
blurRadius: 10.0)
]),
child: AspectRatio(
aspectRatio: cardAspectRatio,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
Image.asset(images[i], fit: BoxFit.cover),
Align(
alignment: Alignment.bottomLeft,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(
horizontal: 16.0, vertical: 8.0),
child: Text(title[i],
style: TextStyle(
color: Colors.white,
fontSize: 25.0,
fontFamily: "SF-Pro-Text-Regular")),
),
SizedBox(
height: 10.0,
),
Padding(
padding: const EdgeInsets.only(
left: 12.0, bottom: 12.0),
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 22.0, vertical: 6.0),
decoration: BoxDecoration(
color: Colors.blueAccent,
borderRadius: BorderRadius.circular(20.0)),
child: Text("Read Later",
style: TextStyle(color: Colors.white)),),),],),)],)),),),
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => )),
),);
cardList.add(cardItem);
}
return Stack(
children: cardList,
);}),);}}
data-
List<String> images = [
"assets/image_04.jpg",
"assets/image_03.jpg",
"assets/image_02.jpg",
"assets/image_01.png",
];
List<String> title = [
"Hounted Ground",
"Fallen In Love",
"The Dreaming Moon",
"Jack the Persian and the Black Castel",
];
you can change your data structure to place your destination page and put it to MaterialPageRoute builder method.
List pages = [
{
"image" : "assets/image_01.png",
"title" : "Hounted Ground 1",
"page" : YOURWIDGET(),
},
{
"image" : "assets/image_02.png",
"title" : "Hounted Ground2",
"page" : YOURWIDGET(),
},
{
"image" : "assets/image_03.png",
"title" : "Hounted Ground 3",
"page" : YOURWIDGET(),
}
];
// in your cards page build method
// note that you should place below code in a multi widget support widget like Column(), ListView(), ...
Column(
children: pages.map((page) {
return Card(
// card content and attributes
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext ctx) {
return page['page'];
}
));
}
);
}).toList()
);
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (_) => (i == 0)
? pageOne()
: (i == 1)
? pageTwo()
: (i == 2)
? pageThree()
: pageFour())),