flutter [Only static members can be accessed in initializers] - flutter

I am a true beginner in flutter and dart.
I have a problem concerning playing youtube videos using [ youtube_player_flutter: ^6.1.1]
I create a Json file with youtube links and I want to link it with [ youtube_player_flutter: ^6.1.1]. but it always displays the error message [Only static members can be accessed in initializers]
#override
Widget build(BuildContext context) {
// this function is called before the build so that
// the string assettoload is avialable to the DefaultAssetBuilder
setasset();
// and now we return the FutureBuilder to load and decode JSON
return FutureBuilder(
future:
DefaultAssetBundle.of(context).loadString(assettoload, cache: true),
builder: (context, snapshot) {
List mydata = json.decode(snapshot.data.toString());
if (mydata == null) {
return Scaffold(
body: Center(
child: Text(
"Loading",
),
),
);
} else {
return quizpage(mydata: mydata);
}
},
);
}
}
class quizpage extends StatefulWidget {
final dynamic mydata;
////////var youtubeUrl;
quizpage({Key key, #required this.mydata}) : super(key: key);
#override
_quizpageState createState() => _quizpageState(mydata);
}
class _quizpageState extends State<quizpage> {
var mydata;
_quizpageState(this.mydata);
int marks = 0;
int i = 1;
#override
void setState(fn) {
if (mounted) {
super.setState(fn);
}
}
YoutubePlayerController _controller;
#override
void initState() {
_controller = YoutubePlayerController(
initialVideoId: YoutubePlayer.convertUrlToId(mydata[4]["1"]));
super.initState();
}
void nextquestion() {
setState(() {
if (i < 10) {
i++;
} else {
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => resultpage(marks: marks),
));
}
The problem is that I want to make the [String videoURL ] plays the list of videos in my json data file.
Thanks in advance.

Possibility is that you coded the variable mydata twice. This is the format you should follow. And in order to make use of the variable from the StatefulWidget from the constructor, use widget.mydata. Don't have to declare it twice.
Code:
class Quizpage extends StatefulWidget {
final dynamic mydata;
quizpage({Key key, #required this.mydata}) : super(key: key);
#override
_QuizpageState createState() => _QuizpageState();
}
class _QuizpageState extends State<Quizpage> {
/*
You can make use of your mydata in this class like this:
widget.mydata, and you will be able to make it work
*/
Color colortoshow = Colors.indigoAccent;
Color right = Colors.green;
Color wrong = Colors.red;
int marks = 0;
int i = 1;
// String videoURL ="https://www.youtube.com/watch?v=2OAdfB2U88A&t=593s";
YoutubePlayerController _controller;
// Use like this to make use of your array mydata
String videoURL = widget.myData[4]["1"];
#override
void initState() {
_controller = YoutubePlayerController(
initialVideoId: YoutubePlayer.convertUrlToId(videoURL));
super.initState();
}
}
Also, this is for coding point of view. Please follow the correct way of naming classes in Flutter. Always use CamelCase or Have your first letter of the class as capital. This is the best practice while you write your code. I hope the above helps you in some sense. Thanks :)

Related

Flutter Custom State Management

What I am trying to achieve is a small custom state management solution that I believe is powerful enough to run small and large apps. The core is based on the ValueNotifier and ValueListenable concepts in flutter. The data can be accessed anywhere in the app with out context since I am storing the data like this:
class UserData {
static ValueNotifier<DataLoader<User>> userData =
ValueNotifier(DataLoader<User>());
static Future<User> loadUserData() async {
await Future.delayed(const Duration(seconds: 3));
User user = User();
user.age = 23;
user.family = 'Naoushy';
user.name = 'Anass';
return user;
}
}
So by using UserData.userData you can use the data of the user whenever you want. Everything works fine until I encountered a problem of providing a child to my custom data consumer that rebuilds the widget when there is a new event fired. The DataLoader class looks like this:
enum Status { none, hasError, loading, loaded }
class DataLoader<T> {
Status status = Status.none;
T? data;
Object? error;
bool get hasError => error != null;
bool get hasData => data != null;
}
which is very simple. Now the class for consuming the data and rebuilding looks like this:
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:testing/utils/dataLoader/data_loader.dart';
class DataLoaderUI<T> extends StatefulWidget {
final ValueNotifier<DataLoader<T>> valueNotifier;
final Widget noneStatusUI;
final Widget hasErrorUI;
final Widget loadingUI;
final Widget child;
final Future<T> future;
const DataLoaderUI(
{Key? key,
required this.valueNotifier,
this.noneStatusUI = const Text('Data initialization has not started'),
this.hasErrorUI = const Center(child: Text('Unable to fetch data')),
this.loadingUI = const Center(
child: CircularProgressIndicator(),
),
required this.child,
required this.future})
: super(key: key);
#override
State<DataLoaderUI> createState() => _DataLoaderUIState();
}
class _DataLoaderUIState extends State<DataLoaderUI> {
Future startLoading() async {
widget.valueNotifier.value.status = Status.loading;
widget.valueNotifier.notifyListeners();
try {
var data = await widget.future;
widget.valueNotifier.value.data = data;
widget.valueNotifier.value.status = Status.loaded;
widget.valueNotifier.notifyListeners();
} catch (e) {
log('future error', error: e.toString());
widget.valueNotifier.value.error = e;
widget.valueNotifier.value.status = Status.hasError;
widget.valueNotifier.notifyListeners();
}
}
#override
void initState() {
super.initState();
log('init state launched');
if (!widget.valueNotifier.value.hasData) {
log('reloading or first loading');
startLoading();
}
}
//AsyncSnapshot asyncSnapshot;
#override
Widget build(BuildContext context) {
return ValueListenableBuilder<DataLoader>(
valueListenable: widget.valueNotifier,
builder: (context, dataLoader, ui) {
if (dataLoader.status == Status.none) {
return widget.noneStatusUI;
} else if (dataLoader.status == Status.hasError) {
return widget.hasErrorUI;
} else if (dataLoader.status == Status.loading) {
return widget.loadingUI;
} else {
return widget.child;
}
});
}
}
which is also simple yet very effective. since even if the initState function is relaunched if the data is already fetched the Future will not relaunch.
I am using the class like this:
class TabOne extends StatefulWidget {
static Tab tab = const Tab(
icon: Icon(Icons.upload),
);
const TabOne({Key? key}) : super(key: key);
#override
State<TabOne> createState() => _TabOneState();
}
class _TabOneState extends State<TabOne> {
#override
Widget build(BuildContext context) {
return DataLoaderUI<User>(
valueNotifier: UserData.userData,
future: UserData.loadUserData(),
child: Text(UserData.userData.value.data!.name??'No name'));
}
}
The error is in this line:
Text(UserData.userData.value.data!.name??'No name'));
Null check operator used on a null value
Since I am passing the Text widget as an argument with the data inside it. Flutter is trying to pass it but not able to since there is no data yet so its accessing null values. I tried with a normal string and it works perfectly. I looked at the FutureBuilder widget and they use a kind of builder and also the ValueLisnableBuilder has a builder as an arguement. The problem is that I am not capable of creating something like it for my custom solution. How can I just pass the child that I want without having such an error and without moving the ValueLisnable widget into my direct UI widget?
I have found the solution.
Modify the DataLoaderUI class to this:
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:testing/utils/dataLoader/data_loader.dart';
class DataLoaderUI<T> extends StatefulWidget {
final ValueNotifier<DataLoader<T>> valueNotifier;
final Widget noneStatusUI;
final Widget hasErrorUI;
final Widget loadingUI;
final Widget Function(T? snapshotData) child;
final Future<T> future;
const DataLoaderUI(
{Key? key,
required this.valueNotifier,
this.noneStatusUI = const Text('Data initialization has not started'),
this.hasErrorUI = const Center(child: Text('Unable to fetch data')),
this.loadingUI = const Center(
child: CircularProgressIndicator(),
),
required this.child,
required this.future})
: super(key: key);
#override
State<DataLoaderUI<T>> createState() => _DataLoaderUIState<T>();
}
class _DataLoaderUIState<T> extends State<DataLoaderUI<T>> {
Future startLoading() async {
widget.valueNotifier.value.status = Status.loading;
widget.valueNotifier.notifyListeners();
try {
var data = await widget.future;
widget.valueNotifier.value.data = data;
widget.valueNotifier.value.status = Status.loaded;
widget.valueNotifier.notifyListeners();
} catch (e) {
log('future error', error: e.toString());
widget.valueNotifier.value.error = e;
widget.valueNotifier.value.status = Status.hasError;
widget.valueNotifier.notifyListeners();
}
}
#override
void initState() {
super.initState();
log('init state launched');
if (!widget.valueNotifier.value.hasData) {
log('reloading or first loading');
startLoading();
}
}
//AsyncSnapshot asyncSnapshot;
#override
Widget build(BuildContext context) {
return ValueListenableBuilder<DataLoader<T>>(
valueListenable: widget.valueNotifier,
builder: (context, dataLoader, ui) {
if (dataLoader.status == Status.none) {
return widget.noneStatusUI;
} else if (dataLoader.status == Status.hasError) {
return widget.hasErrorUI;
} else if (dataLoader.status == Status.loading) {
return widget.loadingUI;
} else {
return widget.child(dataLoader.data);
}
});
}
}
and use it like this:
DataLoaderUI<User>(
valueNotifier: UserData.userData,
future: UserData.loadUserData(),
child: (user) {
return Text(user!.name ?? 'kk');
});
Take a look at my version of the same sort of state management approach here: https://github.com/lukehutch/flutter_reactive_widget

LateInitializationError with Future

I hope you could help me!
Error saying 'tables' has not been initiliazed. But when I set tables = [] instead of
widget.data.then((result) {tables = result.tables;})
it works. I think the problem comes from my app state data which is a Future.
My simplified code:
class NavBar extends StatefulWidget {
final Future<Metadata> data;
const NavBar({Key? key, required this.data}) : super(key: key);
#override
State<NavBar> createState() => _NavBarState();
}
class _NavBarState extends State<NavBar> {
late List<MyTable> tables;
#override
void initState() {
widget.data.then((result) {
tables = result.tables;
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: buildPages(page.p)
)
);
}
Widget buildPages(index){
switch (index) {
case 0:
return ShowTablesNew(tables: tables);
case 1:
return const Details();
case 2:
return const ShowTables();
default:
return const ShowTables();
}
}
}
Future doesn't contain any data. It's an asynchronous computation that will provide data "later". The initialization error happens because the variable 'tables' is marked as late init but is accessed before the future is completed, when in fact it's not initialized yet.
Check this codelab for async programming with dart.
For your code you can use async/await in the initState method doing something like this
String user = '';
#override
void initState() {
asyncInitState();
super.initState();
}
void asyncInitState() async {
final result = await fetchUser();
setState(() {
user = result;
});
}
but since you're using a list of custom objects the most straightforward way is probably to use a FutureBuilder widget

Storing certain value in Widget build / Flutter

I've a question:
In my Widget build(BuildContext context), I want to store a certain value,
final userName = book.owner
(book is the reference to the certain value from Firestore)
But it's done not in the right way to my lack of knowledge. I'd appreciate if someone could guide through that.
Thank you in advance!
Snippet of my code
class BookView extends StatefulWidget {
final Book book;
BookView({Key key, #required this.book}) : super(key: key);
DatabaseMethods databaseMethods = new DatabaseMethods();
var userName;
#override
_BookViewState createState() => _BookViewState(book);
}
class _BookViewState extends State<BookView> {
Book book;
_BookViewState(this.book);
String userName;
#override
void initState() {
userName = book.owner;
super.initState();
}
// final Book book;
createChatroomAndStartConversation({var userName}) {
if (userName != Constants.myName) {
String roomId = getChatRoomId(userName, Constants.myName);
List<String> users = [userName, Constants.myName];
Map<String, dynamic> chatRoomMap = {
"Users": users,
"roomId": roomId,
};
DatabaseMethods().createChatRoom(roomId, chatRoomMap);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ConversationScreen(roomId, userName)),
);
} else {
print("You cannot send msg to your self");
}
}
#override
Widget build(BuildContext context) {
//widget.book;
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
...
FlatButton(
child: Text(
"Get contact with",
style: TextStyle(color: Colors.white),
),
color: Colors.blue,
onPressed: () {
createChatroomAndStartConversation(
userName: userName);
...
}
Snippet of Value not in range: 1
getChatRoomId(String a, String b) {
if (a.substring(0, 1).codeUnitAt(0) > b.substring(0, 1).codeUnitAt(0)) {
return "$b\_$a";
} else {
return "$a\_$b";
}
}
It's not a good practice to store any data in build() method, because this method is invoked too many times to do the such kind of move. Consider using StatefulWidget to store any state you have in the widget, for the very beginning. When you use this widget, you can define this all in such way:
class YourWidget extends StatefulWidget {
#override
_YourWidgetState createState() => _YourWidgetState();
}
class _YourWidgetState extends State<YourWidget> {
String userName;
#override
void initState() {
userName = book.owner;
super.initState()
}
#override
Widget build(BuildContext context) {
return Container(child: Text(userName),);
}
}
Here, in initState() you can retrieve value from book and set it to userName. But for more complex and bigger applications, consider using StateManagement solutions and some kind of architectural patterns i.e. Riverpod, Provider, MobX, BLoC.. Because changing the state via setState() method will cause rebuilding whole child widget tree, which could freeze whole UI in complex app.
UPD to 'Snippet of my code':
According to your code, if you are using a 'book' from Widget, not its state - use widget.book, in such way you have access to widget members, because of this you don't need a constructor of state. So, due to these changes, your code might looks like:
class BookView extends StatefulWidget {
final Book book;
BookView({Key key, #required this.book}) : super(key: key);
// You DON'T need this here, because you are retrieving these methods
// inside your state via DatabaseMethods constructor
DatabaseMethods databaseMethods = DatabaseMethods();
#override
_BookViewState createState() => _BookViewState(book);
}
class _BookViewState extends State<BookView> {
String userName;
#override
void initState() {
// Using widget.book to retrieve Book object from state's widget
userName = widget.book.owner;
super.initState();
}
createChatroomAndStartConversation({var userName}) {
if (userName != Constants.myName) {
String roomId = getChatRoomId(userName, Constants.myName);
// Also, it's just a recommendation, try to omit local variables types
// because they are already known with List type (String). Also, this
// all is about chatRoomMap
var users = <String>[userName, Constants.myName];
final chatRoomMap = <String, dynamic>{
"Users": users,
"roomId": roomId,
};
DatabaseMethods().createChatRoom(roomId, chatRoomMap);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ConversationScreen(roomId, userName)),
);
} else {
print("You cannot send msg to your self");
}
}
#override
Widget build(BuildContext context) {
// your widgets here
}
}
UPD 2:
Second trouble and issue with 'Snippet of Value not in range: 1'. I could to reproduce it with given value of 'a' as empty string. So, your function invocation is like getChatRoomId('', 'user123'), because of empty 'userName', substring function can't take values from range [0, 1), so exception is raised.

How acess provider outside a Widget?

I have an application that I making some tests with RxDart using observables and subjects. So I make this code:
class CompanyList extends StatefulWidget {
const CompanyList({Key key}) : super(key: key);
#override
State<StatefulWidget> createState() => new CompanyListState();
}
class CompanyListState extends State<CompanyList> {
CompanyController companyController = new CompanyController();
List<dynamic> companyList = [];
#override
void initState() {
super.initState();
getActiveCompanys();
companyController.loadMoreData$.listen((value) {
print(value);
});
}
#override
void dispose() {
super.dispose();
}
getActiveCompanys() async {
companyController.getActiveCompanys().then((value) {
for (var i = 0; i < 10; i++) {
setState(() {
companyList.add(value[i]);
});
}
}).catchError((_) {
print('ENTROU NO ERRO');
});
}
getCompanyData(company) {
Navigator.push(
context,
PageTransition(
type: PageTransitionType.leftToRight,
child: CompanyScreen(
company: company,
),
),
);
}
#override
Widget build(BuildContext context) {
final double screenHeight = MediaQuery.of(context).size.height;
final double screenWidth = MediaQuery.of(context).size.width;
... the code goes on!
I want to get a provider data like this CompanyController companyController = new Provider.of<CompanyController>(context, listen: false); Where I put CompanyController companyController = new CompanyController();
But I don't have a context and I need this instance to make the RxDart subject and observables to work together.
You need a reference to the context in order to use the Provider, the State object has a reference as a field of its class, but this reference shouldn't be used before initState() is called, so just use the reference you need inside initState() and create everything you need there:
initState() {
super.initState();
companyController = new Provider.of<CompanyController>(context, listen: false);
// use companyController here.
...
}
Also, remember to dispose all objects that need to be disposed at dispose().

What is the right way to implement a callback/listener pattern?

Sorry if this is a novice question. I have the following repo file:
class TwitterRepo {
final TwitterRepoCallback _callback;
TwitterRepo(this._callback){
// do stuff
}
}
abstract class TwitterRepoCallback{
void onEvent();
}
In my UI file I have the following:
class TweetList extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _TweetListState();
}
}
class _TweetListState extends State<TweetList> implements TwitterRepoCallback {
final TwitterRepo _twitterRepo = TwitterRepo(this);
// other stuff like initState, build and onEvent
}
There is an error on
final TwitterRepo _twitterRepo = TwitterRepo(this);
where I use "this", Invalid reference to 'this' expression.
I'm at a loss on how to pass in my callback to receive events.
Try this.
class ParentPageState extends State<ParentPage> implement Callback{
...
#override
void callback(){
...
}
#override
void callback1(String str){
....
}
#override
Widget build(BuildContext context){
return Scaffold(
body : Container(
child : ChildPage(callback : this.callback, callback1 : this.callback1)
)
);
}
}
And ChildPage
import .....
//Main Point
typedef Callback = void Function();
typedef Callback1 = void Function(String str);
class ChildPage extends StatelessWidget{
final Callback _callback;
final Callback1 _callback1;
ChildPage({Callback callback, Callback1 callback1}): _callback : callback, _callback1 : callback1;
.....
#override
Widget build(BuildContext context){
return Container(
child : InkWell(
onPressed : (){
this._callback();
this._callback1("test");
},
child : ....
)
);
}
This is may have issue. The main point is "typedef"
I probably wouldn't use callbacks for this type of need. Instead I'd use some kind of InheritedWidget like system to grab data and propagate it down the widget tree. I know you just started, but a great tool is the Provider package. To do what you're trying to do here it'd look something like this:
class TwitterRepo extends ChangeNotifier{
//construct Repo
TwitterRepo(){
_setupNetworkListener();
}
List<Data> data = [];
//set up the way to listen to and get data here then add it to your list,
//finally notify your listeners of the data changes
_setupNetworkListener()async{
var _data = await gettingInfo();
data.addAll(_data);
notifyListeners();
}
}
class TwitterRepoUI extends StatefulWidget {
#override
_TwitterRepoUIState createState() => _TwitterRepoUIState();
}
class _TwitterRepoUIState extends State<TwitterRepoUI> {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<TwitterRepo>(
builder: (context)=> TwitterRepo(),
child: Consumer<TwitterRepo>(
builder: (context, model, child){
return ListView.builder(
itemCount: model.data.length,
itemBuilder: (context, index){
return Center(
child: Text(index.toString()),
);
});
},
),
);
}
}
If you want to use the callback to notify the UI to render some new data, you may want to use Future or Stream. Anyway, the question is how to implement a callback/listener so here I give you some examples:
You can't declare a variable using this, you could initialize the variable on the constructor
_TweetListState() {
_twitterRepo = TwitterRepo(this);
}
or inside initState()
#override
void initState() {
super.initState();
_twitterRepo = TwitterRepo(this);
}
A better way to do this would be:
final TwitterRepo _twitterRepo = TwitterRepo();
#override
void initState() {
super.initState();
_twitterRepo.listen(this);
}
And the listen method implemented on TwitterRepo would set the callback
You could also use VoidCallback instead of TwitterRepoCallback:
#override
void initState() {
super.initState();
_twitterRepo.listen(() {
// Do stuff
});
}
Or a callback function using Function(String a, int b) instead of TwitterRepoCallback
#override
void initState() {
super.initState();
_twitterRepo.listen((a, b) {
// Do stuff
});
}