Have looked at similar questions, can't see any common mistakes. Once the factory seems to create the object with no issues. However, calling any of the methods generates a NoSuchMethodError. Been debugging for days, out of ideas. Have similar code using data models of that general layout with no issues.
This is the code for the data model
class Performer {
String avatar, header, name, username;
int id, subscribePrice;
bool isRealPerformer,
isPerformer,
hasStories,
hasStream,
isPaywallRestriction;
Performer(
{this.avatar,
this.header,
this.name,
this.username,
this.id,
this.subscribePrice,
this.isRealPerformer,
this.isPerformer,
this.hasStories,
this.hasStream,
this.isPaywallRestriction});
factory Performer.fromJson(Map<String, dynamic> performer) {
return Performer(
avatar: performer["avatar"],
header: performer["header"],
name: performer["name"],
username: performer["username"],
id: performer["id"],
subscribePrice: performer["subscribePrice"],
isRealPerformer: performer["isRealPerformer"],
isPerformer: performer["isPerformer"],
hasStories: performer["hasStories"],
hasStream: performer["hasStream"],
isPaywallRestriction: performer["isPaywallRestriction"]);
}
}
This is the code that populates the models
Future<List<Performer>> getSubscriptions() async {
List<Performer> performers = [];
String url = "some API url";
String res = await _callServer(url);
if (res.isNotEmpty) {
List<dynamic> payload = json.decode(res);
payload.forEach((element) {
performers.add(new Performer.fromJson(element));
});
return performers;
} else return performers;
}
Future<Performer> getPerformer(int performerID) async {
List<Performer> subs = await getSubscriptions();
Performer performer;
int prefIndex;
for (int x = 0; x < subs.length; x++) {
if (subs[x].id == performerID){
performer = subs[x];
break;
}
}
if (performer.avatar != null) {
print("found ${performer.username}");
return performer;
} else return null;
}
This is the code that generates the UI element based on the model
class ProfilePic extends StatefulWidget {
final int id;
ProfilePic({Key key, #required this.id}) : super();
#override
State<StatefulWidget> createState() => _profilePicState();
}
class _profilePicState extends State<ProfilePic> {
Performer performer;
#override
void initState() {
// TODO: implement initState
super.initState();
Backend().getPerformer(widget.id).then((value) {
performer = value;
setState(() {});
});
}
#override
Widget build(BuildContext context) {
print("profile for: ${widget.id}");
return Container(
child: performer == null ? Container() : CircleAvatar(
radius: 30.0,
backgroundImage:
NetworkImage(performer.avatar),
backgroundColor: Colors.transparent,
),
);
}
In your getSubscriptions, try
payload.forEach((element) {
performers.add(new Performer.fromJson(Map.from(element)));
});
Nothing wrong with the code was returning the post ID not the User ID to the function, hence the null error.
Related
I want to fetch paginated data in my flutter app from my website using REST API.
I have integrated pagination and now it is started working.
But the problem is that on loading more data, I am getting duplicate data instead of getting new data.
I think I am doing something wrong to increment the page no. in the _getAllNews() method
Here is my complete code, and I think I am doing very small mistake in this.
class Tedd extends StatefulWidget {
#override
_TeddState createState() => _TeddState();
}
class _TeddState extends State<Tedd> {
List<NewsModel> _newsList = [];
bool isLoading = true;
int currentPage = 1;
bool hasReachedEnd = false;
ScrollController scrollController = ScrollController();
_getAllNews(page) async {
setState(() {
isLoading = true;
});
var articles = await http.get(Uri.parse(
"https://pkbhai.com/myprojects/kids-stories/api/all-stories?page=${page}"));
var result = json.decode(articles.body);
print(result);
result['data'].forEach((data) {
var news = NewsModel();
news.id = data["id"];
news.articleTitle = data["name"];
if (mounted) {
setState(() {
_newsList.add(news);
isLoading = false;
currentPage = currentPage++;
});
}
});
}
void handleNext() {
scrollController.addListener(() async {
if (scrollController.position.maxScrollExtent ==
scrollController.position.pixels) {
_getAllNews(currentPage);
}
});
}
#override
void initState() {
_getAllNews(currentPage);
handleNext();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
controller: scrollController,
itemCount: _newsList.length + (isLoading ? 1 : 0),
itemBuilder: (context, index) {
if (index == _newsList.length) {
return Center(
child: CircularProgressIndicator(),
);
}
return Container(
height: 150, child: Text(_newsList[index].articleTitle!));
},
),
);
}
}
What am I doing wrong?
i have check the api, now the problem of the repeated data is coming from the api url.
after check the api and test base on this 3 parameter,
current_page= 1,
from=1,
last_page=3,
but is not working...
solution:
contact the developer of the api and check the api or recreate another working pagination url for you to make request
I created a widget that, depending on the focus of its FocusNode, either becomes a TextField or a Text. It is working perfectly and here is the code (didn't include it here as its large).
The problem is, Text and TextField have really alot of parameters to style them, and I find it not optimal to copy all these parameters into the constructor of my new hybrid widget just to pass them to these two widgets in the new build method without doing anything else with them.
For example TextField has over 50 parameters in its constructor, is the only way to compose it with another widget and still get all these options to style the TextField, is by copying each one of these parameters into my constructor, and then not doing anything with them other than passing them down to the TextField?
So is there some design pattern or some solution that lets the parameters of these 2 widgets be available in the constructor of the new widget?
note: see the comment of
M. Azyoksul on Gunter's comment here also for more context.
minimal example of the problem:
// this widget is from external library (not under my control)
class WidgetA extends StatelessWidget {
// very long list of fields
A1 a1;
A2 a2;
... (long list of fields)
// constructor
WidgetA(this.a1, this.a2, ...);
}
// this widget is from external library
class WidgetB extends StatelessWidget {
// very long list of fields
B1 b1;
B2 b2;
... (long list of fields)
// constructor
WidgetB(this.b1, this.b2, ...);
}
// now this is the widget I want to create
class HybridWidget extends StatelessWidget {
// time consuming: I copy all the fields of WidgetA and
// WidgetB into the new constructor just to pass them as they are without doing anything else useful on them
A1 a1;
A2 a2;
...
B1 b1;
B2 b2;
...
// the new constructor: (not optimal at all)
HybridWidget(this.a1,this.a2,...,this.b1,this.b2,...);
#override
Widget build(BuildContext context) {
// for example:
if(some condition)
return Container(child:WidgetA(a1,a2, ...),...); <--- here is the problem, I am not doing anything other than passing the "styling" parameters as they were passed to me, alot of copy/paste
if(other condition)
return Container(Widget2(b1,b2, ... ),...); <--- and here is the same problem
//... other code
}
}
Builder pattern might work (not sure if that's the right term).
First define our Function signatures:
typedef TextBuilder = Widget Function(String text);
typedef TextFieldBuilder = Widget Function(TextEditingController, FocusNode);
Those will be used in your DoubleStatetext...
DoubleStateText(
initialText: 'Initial Text',
textBuilder: (text) => Text(text, style: TextStyle(fontSize: 18)),
textFieldBuilder: (controller, focusNode) =>
TextField(controller: controller, focusNode: focusNode, cursorColor: Colors.green,)
),
... so instead of passing all the args to DoubleStateText we pass it builders (functions) that wrap the Text and TextField with all the args we want. Then DoubleStateText just calls the builders instead of creating the Text/TextField itself.
Changes to DoubleStateText:
class DoubleStateText extends StatefulWidget {
final String Function()? onGainFocus;
final String? Function(String value)? onLoseFocus;
final String initialText;
// NEW ==================================================
final TextBuilder textBuilder;
// NEW ==================================================
final TextFieldBuilder textFieldBuilder;
final ThemeData? theme;
final InputDecoration? inputDecoration;
final int? maxLines;
final Color? cursorColor;
final EdgeInsets padding;
final TextStyle? textStyle;
const DoubleStateText({
Key? key,
this.onGainFocus,
this.onLoseFocus,
required this.initialText,
required this.textBuilder, // NEW ==================================================
required this.textFieldBuilder, // NEW ==================================================
this.theme,
this.inputDecoration,
this.maxLines,
this.cursorColor,
this.padding = EdgeInsets.zero,
this.textStyle,
}) : super(key: key);
#override
State<DoubleStateText> createState() => _DoubleStateTextState();
}
class _DoubleStateTextState extends State<DoubleStateText> {
bool _isEditing = false;
late final TextEditingController _textController;
late final FocusNode _focusNode;
late final void Function() _onChangeFocus;
#override
void initState() {
super.initState();
_textController = TextEditingController(text: widget.initialText);
_focusNode = FocusNode();
// handle Enter key event when the TextField is focused
_focusNode.onKeyEvent = (node, event) {
if (event.logicalKey == LogicalKeyboardKey.enter) {
setState(() {
String? text = widget.onLoseFocus?.call(_textController.text);
_textController.text = text ?? widget.initialText;
_isEditing = false;
});
return KeyEventResult.handled;
}
return KeyEventResult.ignored;
};
// handle TextField lose focus event due to other reasons
_onChangeFocus = () {
if (_focusNode.hasFocus) {
String? text = widget.onGainFocus?.call();
_textController.text = text ?? widget.initialText;
}
if (!_focusNode.hasFocus) {
setState(() {
String? text = widget.onLoseFocus?.call(_textController.text);
_textController.text = text ?? widget.initialText;
_isEditing = false;
});
}
};
_focusNode.addListener(_onChangeFocus);
}
#override
void dispose() {
_textController.dispose();
_focusNode.removeListener(_onChangeFocus);
_focusNode.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
Widget child;
if (!_isEditing) {
child = InkWell(
onTap: () {
setState(() {
_isEditing = true;
_focusNode.requestFocus();
});
},
//child: Text(_textController.text, style: widget.textStyle),
// NEW: use the builders ==========================================
child: widget.textBuilder(_textController.text));
} else {
// NEW: use the builders ==========================================
child = widget.textFieldBuilder(_textController, _focusNode);
/*child = TextField(
focusNode: _focusNode,
controller: _textController,
decoration: widget.inputDecoration,
maxLines: widget.maxLines,
cursorColor: widget.cursorColor,
);*/
}
child = Padding(
padding: widget.padding,
child: child,
);
child = Theme(
data: widget.theme ?? Theme.of(context),
child: child,
);
return child;
}
}
Here's an example of how the above would be used:
typedef TextBuilder = Widget Function(String text);
typedef TextFieldBuilder = Widget Function(TextEditingController, FocusNode);
class CompositeWidgetContent extends StatefulWidget {
#override
State<CompositeWidgetContent> createState() => _CompositeWidgetContentState();
}
class _CompositeWidgetContentState extends State<CompositeWidgetContent> {
#override
Widget build(BuildContext context) {
return Center(
child: Container(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SomeOtherFocusable(),
SizedBox(
height: 20,
),
DoubleStateText(
initialText: 'Initial Text',
textBuilder: (text) =>
Text(text, style: TextStyle(fontSize: 18)),
textFieldBuilder: (controller, focusNode) => TextField(
controller: controller,
focusNode: focusNode,
cursorColor: Colors.green,
)),
],
),
),
);
}
}
Notice that the (text) and (controller, focusNode) are not defined anywhere in _CompositeWidgetContentState.
Those are not created/used by the end-user/client.
Those are created within DoubleStateText.
I am not android guy, however, in my view, these principles can be applied here:
make class for parameters
use singleton pattern to share data between your components
if you want to notify other users about changing of data, then you can use observer pattern
Let me clarify what I mean.
1. Make class for parameters
You can create class for parameters:
public class MyParams
{
public int Param_1 { get; set; }
public int Param_2 { get; set; }
public int Param_3 { get; set; }
}
and use them:
class Widget1 extends StatelessWidget {
// very long parameter list
Widget1(MyParams params)
}
2. Use singleton pattern to share data between your components
Let me show singleton pattern via C#:
public sealed class MyParams
{
public int Param_1 { get; set; }
public int Param_2 { get; set; }
public int Param_3 { get; set; }
//the volatile keyword ensures that the instantiation is complete
//before it can be accessed further helping with thread safety.
private static volatile MyParams _instance;
private static readonly object SyncLock = new();
private MyParams() {}
//uses a pattern known as double check locking
public static MyParams Instance
{
get
{
if (_instance != null)
{
return _instance;
}
lock (SyncLock)
{
if (_instance == null)
{
_instance = new MyParams();
}
}
return _instance;
}
}
}
and then you can use it in your widgets:
class HybridWidget extends StatelessWidget {
// how to get parameters of widget1 and widget2 here?
// is there a way other than copying all of them?
public void GetParams()
{
var params = MyParams.Instance;
}
// ... other code is omitted for the brevity
}
Redux tool in React extensively uses these patterns such as Singleton and Observer. Read more in this great article.
UPDATE
If you have many widgets, then you can add all params in params collection WidgetParams.
And then you can access to these parameters from any other widgets.
public sealed class MyParams
{
//the volatile keyword ensures that the instantiation is complete
//before it can be accessed further helping with thread safety.
private static volatile MyParams _instance;
private static readonly object SyncLock = new();
private MyParams()
{
}
//uses a pattern known as double check locking
public static MyParams Instance
{
get
{
if (_instance != null)
{
return _instance;
}
lock (SyncLock)
{
if (_instance == null)
{
_instance = new MyParams();
}
}
return _instance;
}
}
List<WidgetParams> WidgetParams = new List<WidgetParams>();
}
public class WidgetParams
{
/// <summary>
/// Widget name
/// </summary>
public int Name { get; set; }
public object Params { get; set; }
}
You can access to these parameters from any other widgets:
var params = MyParams.Instance.WidgetParams;
So code in your component would look like this:
class HybridWidget extends StatelessWidget {
// how to get parameters of widget1 and widget2 here?
// is there a way other than copying all of them?
public void GetParams()
{
var params = MyParams.Instance.WidgetParams;
}
// ... other code is omitted for the brevity
}
The way I'd handle it would be to pass the Text and Textfield as params instead all the parameters you're asking.
const DoubleStateText({
Key? key,
required this.initialText,
required this.textFieldWidget,
required this.textWidget,
this.onGainFocus,
this.onLoseFocus,
this.padding = EdgeInsets.zero,
}) : super(key: key);
This way I'd keep things simple : the role of the DoubleStateText is only to do a smart switch of widgets.
I'm trying to track location in the background using flutter and to do so I'm using the background_locator plugin. It has been implemented in such a way that there are certain static callback functions that were registered. I've declared a class variable of File type to save the log in the background. The global variable is built at the very beginning of the class.
Issue: While invoking the callback method, the global variable built is becoming null. So though I could see the location log in my console, I couldn't write it to the file as the object is null.
Tries:
I've tried with the exact example provided in their documentation.
I've declared it as non static property and tried to access with the class object.
Tried it out declaring it as static property as well.
Tried building file object with the same path every time needed but it is throwing following issue.
No implementation found for method getApplicationDocumentsDirectory on channel plugins.flutter.io/path_provider
Here is my complete source code for reference.
import 'dart:async';
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'dart:math';
import 'dart:ui';
import 'package:background_locator/background_locator.dart';
import 'package:background_locator/location_dto.dart';
import 'package:background_locator/settings/android_settings.dart';
import 'package:background_locator/settings/ios_settings.dart';
import 'package:background_locator/settings/locator_settings.dart';
import 'package:flutter/material.dart';
import 'package:location_permissions/location_permissions.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart' as ph;
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
ReceivePort port = ReceivePort();
String logStr = '';
bool isRunning = false;
LocationDto? lastLocation;
bool permissionsGranted = false;
static const String isolateName = 'LocatorIsolate';
static int _count = -1;
static File? finalFile;
void requestPermission() async {
var storageStatus = await ph.Permission.storage.status;
if (!storageStatus.isGranted) {
await ph.Permission.storage.request();
}
if (storageStatus.isGranted) {
permissionsGranted = true;
setPrerequisites();
}
setState(() {});
}
static Future<void> init(Map<dynamic, dynamic> params) async {
//TODO change logs
print("***********Init callback handler");
if (params.containsKey('countInit')) {
dynamic tmpCount = params['countInit'];
if (tmpCount is double) {
_count = tmpCount.toInt();
} else if (tmpCount is String) {
_count = int.parse(tmpCount);
} else if (tmpCount is int) {
_count = tmpCount;
} else {
_count = -2;
}
} else {
_count = 0;
}
print("$_count");
await setLogLabel("start");
final SendPort? send = IsolateNameServer.lookupPortByName(isolateName);
send?.send(null);
}
static Future<void> disposeLocationService() async {
await setLogLabel("end");
final SendPort? send = IsolateNameServer.lookupPortByName(isolateName);
send?.send(null);
}
static Future<void> callback(LocationDto locationDto) async {
await setLogPosition(_count, locationDto);
final SendPort? send = IsolateNameServer.lookupPortByName(isolateName);
send?.send(locationDto);
_count++;
}
static Future<void> setLogLabel(String label) async {
final date = DateTime.now();
await _MyAppState().writeToLogFile(
'------------\n$label: ${formatDateLog(date)}\n------------\n');
}
static Future<void> setLogPosition(int count, LocationDto data) async {
final date = DateTime.now();
await _MyAppState().writeToLogFile(
'$count : ${formatDateLog(date)} --> ${formatLog(data)} --- isMocked: ${data.isMocked}\n');
}
static double dp(double val, int places) {
num mod = pow(10.0, places);
return ((val * mod).round().toDouble() / mod);
}
static String formatDateLog(DateTime date) {
return date.hour.toString() +
":" +
date.minute.toString() +
":" +
date.second.toString();
}
static String formatLog(LocationDto locationDto) {
return dp(locationDto.latitude, 4).toString() +
" " +
dp(locationDto.longitude, 4).toString();
}
#override
void initState() {
super.initState();
if (permissionsGranted) {
setPrerequisites();
} else {
requestPermission();
}
}
void setPrerequisites() async {
finalFile = await _getTempLogFile();
if (IsolateNameServer.lookupPortByName(isolateName) != null) {
IsolateNameServer.removePortNameMapping(isolateName);
}
IsolateNameServer.registerPortWithName(port.sendPort, isolateName);
port.listen(
(dynamic data) async {
await updateUI(data);
},
);
initPlatformState();
setState(() {});
}
Future<void> updateUI(LocationDto data) async {
final log = await readLogFile();
await _updateNotificationText(data);
setState(() {
if (data != null) {
lastLocation = data;
}
logStr = log;
});
}
Future<void> _updateNotificationText(LocationDto data) async {
if (data == null) {
return;
}
await BackgroundLocator.updateNotificationText(
title: "new location received",
msg: "${DateTime.now()}",
bigMsg: "${data.latitude}, ${data.longitude}");
}
Future<void> initPlatformState() async {
print('Initializing...');
await BackgroundLocator.initialize();
logStr = await readLogFile();
print('Initialization done');
final _isRunning = await BackgroundLocator.isServiceRunning();
setState(() {
isRunning = _isRunning;
});
print('Running ${isRunning.toString()}');
}
#override
Widget build(BuildContext context) {
final start = SizedBox(
width: double.maxFinite,
child: ElevatedButton(
child: const Text('Start'),
onPressed: () {
_onStart();
},
),
);
final stop = SizedBox(
width: double.maxFinite,
child: ElevatedButton(
child: Text('Stop'),
onPressed: () {
onStop();
},
),
);
final clear = SizedBox(
width: double.maxFinite,
child: ElevatedButton(
child: Text('Clear Log'),
onPressed: () {
clearLogFile();
setState(() {
logStr = '';
});
},
),
);
String msgStatus = "-";
if (isRunning != null) {
if (isRunning) {
msgStatus = 'Is running';
} else {
msgStatus = 'Is not running';
}
}
final status = Text("Status: $msgStatus");
final log = Text(
logStr,
);
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Flutter background Locator'),
),
body: Container(
width: double.maxFinite,
padding: const EdgeInsets.all(22),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[start, stop, clear, status, log],
),
),
),
),
);
}
void onStop() async {
await BackgroundLocator.unRegisterLocationUpdate();
final _isRunning = await BackgroundLocator.isServiceRunning();
setState(() {
isRunning = _isRunning;
});
}
void _onStart() async {
if (await _checkLocationPermission()) {
await _startLocator();
final _isRunning = await BackgroundLocator.isServiceRunning();
setState(() {
isRunning = _isRunning;
lastLocation = null;
});
} else {
// show error
}
}
static Future<void> initCallback(Map<dynamic, dynamic> params) async {
await init(params);
}
static Future<void> disposeCallback() async {
await disposeLocationService();
}
Future<void> locationServicecallback(LocationDto locationDto) async {
await callback(locationDto);
}
static Future<void> notificationCallback() async {
print('***notificationCallback');
}
Future<void> writeToLogFile(String log) async {
await finalFile!.writeAsString(log, mode: FileMode.append);
}
Future<String> readLogFile() async {
return finalFile!.readAsString();
}
static Future<File?> _getTempLogFile() async {
File file =
File('${(await getApplicationDocumentsDirectory()).path}/log.txt');
if (file.existsSync()) {
return file;
} else {
file = await file.create(recursive: true);
}
return file;
}
Future<void> clearLogFile() async {
await finalFile!.writeAsString('');
}
Future<bool> _checkLocationPermission() async {
final access = await LocationPermissions().checkPermissionStatus();
switch (access) {
case PermissionStatus.unknown:
case PermissionStatus.denied:
case PermissionStatus.restricted:
final permission = await LocationPermissions().requestPermissions(
permissionLevel: LocationPermissionLevel.locationAlways,
);
if (permission == PermissionStatus.granted) {
return true;
} else {
return false;
}
case PermissionStatus.granted:
return true;
default:
return false;
}
}
Future<void> _startLocator() async {
Map<String, dynamic> data = {'countInit': 1};
return await BackgroundLocator.registerLocationUpdate(
callback,
initCallback: initCallback,
initDataCallback: data,
disposeCallback: disposeCallback,
iosSettings: const IOSSettings(
accuracy: LocationAccuracy.NAVIGATION, distanceFilter: 0),
autoStop: false,
androidSettings: const AndroidSettings(
accuracy: LocationAccuracy.NAVIGATION,
interval: 5,
distanceFilter: 0,
client: LocationClient.google,
androidNotificationSettings: AndroidNotificationSettings(
notificationChannelName: 'Location tracking',
notificationTitle: 'Start Location Tracking',
notificationMsg: 'Track location in background',
notificationBigMsg:
'Background location is on to keep the app up-tp-date with your location. This is required for main features to work properly when the app is not running.',
notificationIconColor: Colors.grey,
notificationTapCallback: notificationCallback,
),
),
);
}
}
Any help/suggestion would be highly appreciated. Thank you!
The callback function not getting called was an issue I faced inthe version 1.6.12.
I fixed the problem by
forking the background_locator repo on github.
cloning the repo to my computer
opened the location_dto.dart file and went to fromJson function.
added json[Keys.ARG_PROVIDER] ?? '' instead
commited and pushed to my forked repository
in pubspec.yaml, I updated my dependency to point to my forked repository as follows:
background_locator:
git:
url: git#github.com:frankvollebregt/background_locator.git
Please follow these two github issues if you find any problem:
https://github.com/rekabhq/background_locator/issues/320
https://github.com/rekabhq/background_locator/issues/301
background_locator dosen't work on latest flutter sdk versions
for me it's worked when I do this steps
Flutter sdk version should be :3.0.1
In pubspec.yaml file change sdk: ">=2.8.0 <3.0.0"
Don't migrate your code to null safety
in gradle-wrapper.properties change gradle version to gradle-6.5
android/build gradle change ext.kotlin_version to '1.4.31'
android/app/build gradle change compileSdkVersion to 31, minSdkVersion to 19 and targetSdkVersion to 30
This is not a problem with the background locator plugin. When the plugin/library is not registered with Flutter Engine, the 'No implementation' error occurs.
You have been attempting to access the path provider methods from within a Background Isolate. Normally, the path provider plugin will be registered with main isolate.
If you want to use it in your background isolate, you must manually register it with the engine.
Follow the steps below and add these two functions to the Init function in location_service_repositary.dart
if (Platform.isAndroid) PathProviderAndroid.registerWith();
if (Platform.isIOS) PathProviderIOS.registerWith();
Have a good day.
I am trying to make a Telegram client for android using the tdlib flutter port. I am currently attempting to make a contact list of sorts, by requesting it from telegram and making a listview of textbuttons.
The only issue is that since the library is async, I get the contact list after the layout has been initialized. Is it possible to somehow rebuild the layout or update it to make the list load properly.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:fima/services/telegram_service.dart';
import 'package:tdlib/td_api.dart' show TdError;
import 'package:provider/provider.dart';
import 'package:tdlib/td_api.dart' as TdApi;
class ContactListScreen extends StatefulWidget {
#override
_ContactListScreenState createState() => _ContactListScreenState();
}
class _ContactListScreenState extends State<ContactListScreen> {
final String title = 'Contact list';
bool _loadingStep = false;
String _Error;
String route = "initRoute";
List<TextButton> contacts = [];
#override
void initState() {
super.initState();
_getContacts(onError: _handelError,);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
backgroundColor: Color(0xD3232323),
),
body: Container(
child:
ListView (
children: contacts,
),
),
);
}
Future _getContacts(
{
void Function(TdError) onError,
}) async {
final result = await context.read<TelegramService>().send(
TdApi.GetContacts(
),
);
if (result is TdError && onError != null) {
onError(result);
}
TdApi.Users users = result;
for (var i = 0; i < users.totalCount; i++) {
final result = await context.read<TelegramService>().send(
TdApi.GetUser(userId: users.userIds[i]),
);
TdApi.User user = result;
print(user.firstName + " " + user.lastName);
final contact = TextButton(
onPressed: () {
print("Test");
},
child: Text(user.firstName + " " + user.lastName),
);
setState(() {
contacts.add(contact);
});
}
}
void _handelError(TdError error) async {
setState(() {
_loadingStep = false;
_Error = error.message;
});
}
}
I have attempted to use setState, but without much success, could anyone be so kind as to provide me with the solution to this problem?
Using the FutureBuilder might help. It is a widget that builds itself based on the latest snapshot of interaction with a Future.
You can modify your build to return a FutureBuilder something like this:
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _getContacts,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
//Use snapshot data to build by returning your Container with List
}
else{
//Return a CircularProgressIndicator
}
}
}
Refer the documentation on the FutureBuilder class here.
I am completly new to Flutter and Stackoverflow. This is my first question to be in fact so please forgive me if I totaly fail at asking this question. I am trying to make a simple Flutter app that provides a ListView of questions and a checkbox beside each. The user can then choose which question they want to answer. My problem is that when the user checks any of the checkboxes then all get checked and vise versa. The questions themselves are retrieved from a backendless database. The code below is what i have so far. I would really appreciate any help anyone can provide me.
import 'package:flutter/material.dart';
class Questions extends StatefulWidget {
final List<Map> questionList;
Questions(this.questionList);
#override
_QuestionsState createState() => _QuestionsState();
}
class _QuestionsState extends State<Questions> {
bool _questionSelected = true;
Widget _buildQuestionItem(BuildContext context, int index) {
return ListTile(
title: Text(widget.questionList[index]['question']),
trailing: Checkbox(
value: _questionSelected,
onChanged: (bool val){
setState(() {
_questionSelected = val;
});
},
),
);
}
#override
Widget build(BuildContext context) {
return ListView.builder(
padding: EdgeInsets.all(10),
itemBuilder: _buildQuestionItem,
itemCount: widget.questionList.length,
);
}
}
UPDATED:
Thankful for Mohammed Ashab Uddin suggestions I feel that I am close to getting this thing to work but I am still getting an error
"RangeError (index): Invalid value: Valid value range is empty: 0"
I think I should have posted the main.dart code where I set the value of the questionList perhaps it is an order of code execution that causes this error so please find my code for main.dart below in hopes it would help in figuring out this issue.
import 'package:flutter/material.dart';
import 'package:backendless_sdk/backendless_sdk.dart';
import 'package:flutter/rendering.dart';
import 'questions.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'RT Database Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Questions'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
static const String API_HOST = "https://api.backendless.com";
static const String APP_ID = "<APP_ID>";
static const String ANDROID_APP_KEY = "<ANDROID_APP_KEY>";
static const String IOS_APP_KEY = "<IOS_APP_KEY>";
IDataStore<Map> questionsStore = Backendless.data.of('Questions');
List<Map> questionsList = [];
var _questionSelected = false;
#override
void initState() {
super.initState();
_initBackendless();
_enableRealTime();
getQuestions();
}
void _initBackendless() {
Backendless.setUrl(API_HOST);
Backendless.initApp(APP_ID, ANDROID_APP_KEY, IOS_APP_KEY);
}
void _enableRealTime() {
EventHandler<Map> rtHandlers = questionsStore.rt();
rtHandlers.addCreateListener((question) {
setState(() {
questionsList = List.from(questionsList);
questionsList.add(question);
});
});
rtHandlers.addUpdateListener((question) {
setState(() {
questionsList = List.from(questionsList
.map((m) => m['objectId'] == question['objectId'] ? question : m));
});
});
rtHandlers.addDeleteListener((question) {
setState(() {
questionsList = List.from(questionsList);
questionsList.removeWhere((m) => m['objectId'] == question['objectId']);
});
});
}
void _selectQuestion(bool newValue) {
setState(() {
_questionSelected = newValue;
});
}
void getQuestions() {
DataQueryBuilder queryBuilder = DataQueryBuilder()
..pageSize = 100
..sortBy = ['created'];
questionsStore
.find(queryBuilder)
.then((response) => setState(() => questionsList = response));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("My Life History"),
),
body: FractionallySizedBox(
heightFactor: 0.5,
child: Questions(questionsList),
),
);
}
}
The variable _questionSelected is a global variable. All the checkbox widgets are using this variable as the value. Therefore, when the variable changes on the onChanged() function, all the values are also changed to the value of _questionSelected.
In this case, you need to keep track of all the values of the checkbox widget. So, you should use an array rather than a single variable.
What I usually do is, create a new list that will contain only the selected elements.
Remove an element if it is not selected and add an element if it is selected.
//generate a list of false values with the length of questionList
List<bool> _questionSelected;
initState(){
_questionSelected = List<bool>.filled(questionList.length, false, growable: true);
super.initState();
}
Widget _buildQuestionItem(BuildContext context, int index) {
return ListTile(
title: Text(widget.questionList[index]['question']),
trailing: Checkbox(
value: _questionSelected[index],
onChanged: (bool val){
setState(() {
_questionSelected[index] = val;
});
},
),
);
}