reusable classes in flutter - flutter

I have a page like the following. This page has two buttons. One of them is sending the form to the relevant place. In my case to the instructor. But, this one is not important for now. The second button is the important one. Anyway, the second one is saving the form as a draft. So, I know I need to save the data that is entered, in SQLite. Then when he/she want to edit the form again I need to pull the data from SQLite then fill the relevant places. Here is the question. When I click to edit button. The page below will show up with the content that is filled before. However, for this task, it does not make sense to code again the screen below for the edit button. So I need to reuse the page below. How can I do it? Any idea. Is there any content that could be helpful? Please attach a link, video, etc. It doesn't matter. I just want to learn how to do it. I hope I could explain the situation. If any more information is required to understand the situation feel free to ask for it.

There are many ways to achieve what you need. But basically, you need to check whether there is saved data or not? If there is saved data then show it otherwise show an empty page. Simple example:
class MyPageData {
String firstField;
String secondField;
// ... fields with page info
}
class MyFormPage extends StatefulWidget {
#override
State<StatefulWidget> createState() => _MyForPageState();
}
class _MyForPageState extends State<MyFormPage>{
MyPageData _savedData;
#override
void initState() {
super.initState();
_savedData = loadDataFromDb();
}
#override
Widget build(BuildContext context) {
String myFirstField = "";
String mySecondField = "";
// other fields
if (_savedData != null){
myFirstField = _savedData.firstField;
mySecondField = _savedData.secondField;
// other fields
}
// render page with fields values above
}
}
Here MyPageData is a model of all the data on your page. So it's easier to work with. Also, this data is saved to db and restored from db in the future.
MyFormPage is a stateful widget for your form page. There is a loadDataFromDb method that is used to load saved data. Then in the build method, we check whether there is saved data or not. If there is data we filled initial values for all fields on our page and use those fields as initial values for the widgets from which our page is constructed. So if there is no data saved then _savedData will be null and all widgets will have empty initial values.

If you put your page in a widget like this
class MyPage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
body: // your page here
)
}
}
you can open it in different locations.
_onButton1Click(BuildContext context){
// do something button 1 specific
final route = MaterialPageRoute(builder: (context) => MyPage());
Navigator.of(context).push(route);
}
_onButton2Click(BuildContext context){
// do something button 2 specific
final route = MaterialPageRoute(builder: (context) => MyPage());
Navigator.of(context).push(route);
}

Related

How to get TextField content from screen to screen?

still new to flutter and I was trying to take multiple values from TextFields in a form to display them in a new screen inside multiple Text elements.
Can someone explain how to do it ?
There are three ways to do it
First method: You can define a class and assign values to it like this:
class Global(){
String text;
}
and then you can import it and assign values or use it like this:
// assign data
Global().text = TextField_controller; // I assume you have already implemented a TextField
// use it
Text(Global().text)
This method is good for passing data between multiple pages but it's not recommended because you can't update the screen when the value changes, it's only good when you need to pass a static variable between multiple pages, for example a user name
Second method: passing data to next page directly
Make the SecondScreen constructor take a parameter for the type of data that you want to send to it. In this particular example, the data is defined to be a String value and is set here with this.text.
class SecondScreen extends StatelessWidget {
final String text;
SecondScreen({Key key, #required this.text}) : super(key: key);
...
Then use the Navigator in the FirstScreen widget to push a route to the SecondScreen widget. You put the data that you want to send as a parameter in its constructor.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondScreen(text: 'Hello',),
));
this method is great for passing data from a parent page to a child page however it can quickly become a nightmare if you want to pass the data to several children down the widget tree or move data back to the parent widget, in that case you can use method 1 or
Third method: using Provider, which is the recommended way, it is similar to the first method but with provider you can ``notify``` all of the listeners to the provider class, meaning you can update the widget whenever the the variable updates, I strongly recommend reading the documentation or watching some YouTube videos, but in short you can use it like this:
after installing the provider package you define your class:
Global extends ChangeNotifierProvider(){
String text;
}
and then add it to the root of your app in main.dart:
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Global()),
),
],
child: MyApp(),
);
}
and then you define your provider wherever you need to use it
Provider.of<Global>(context, listen: false); // Note that if you want to listen to changes you have to set listen to true
// then you can access your variable like in method 1
insatnce.text = TextField_controller;
// and then you can use it anywhere
Text(instance.text);
again if you find this confusing read the documentation or watch some videos

Maintain state between rebuilds

I have a StatefulWidget which is built from a list of fields.
It looks something like this:
class MultiFieldForm extends StatefulWidget {
/// constructor
const MultiFieldForm(this.fields);
/// Fields
final List<Field> fields;
#override
_MultiFieldFormState createState() => _MultiFieldForm();
}
class _MultiFieldFormState extends State<MultiFieldForm> {
/// constructor
_MultiFieldFormState();
/// Fields the user has minimized
List<Field> collapsedFields = [];
/// Fields the user has filled with the values
Map<Field,String> filledFields = {};
#override
Widget build(BuildContext context) {
// build code here
}
}
The state takes this list of fields and creates a form for each field with a text box to fill out, and a button to submit the whole thing.
Now this works, except if a new field is added to the form while the user is filling it out, and entirely new widget is built in the place of the old one. For the user this appear like their form has just been completely wiped. And indeed they have to start over again. This is not ideal, because I actually have new fields appearing all the time.
What I would like to do is have access to the old state when building the new state, so I can copy what the user has already filled and adjust it so that it fits the new form.
This is sort of what didUpdateWidget does, but as far as I can tell, I only get access to the old widget and not its state.
How can I transfer the state of the old widget to the new widget?

How to launch a another screen with different functions based on the user onTap

I am currently working on a Math app that has a category screen which has Cards for different math operations(plus, Minus, Division). when user taps on it displays the next screen with questions and answers. I had create a separate class for question and answer generation and inside it I had created methods for each math operation like below
class QandAgeneration{
//Perform the addition quiz generation
Map addition(){}
//Perform the subtraction quiz generation
Map subtraction(){}
//Perform the division quiz generation
Map division(){}
// Generate answers
List generateAnswers(){}
}
In the next screen I want to display the output of the user selected operation eg:- QandAgeneration.addition() as widgets and those widgets state changed to display the next question when user press a button to select the answer. I think I can do it by using if else by passing the String value from category screen to next screen by using constructor with string parameter. Then I can do it as below
if(widget.selectedType == 'addition'){
QandAgeneration.addition()
}
else if(widget.selectedType == 'subtraction'){
QandAgeneration.subtraction()
}else{
QandAgeneration.division()
}
Is this is the best way to achieve this function. Is there any other method?
Create a class for another screen and pass to this screen needed parameters. On another screen implement functionality based on passed data. For example:
class AnotherScreen extends StatelessWidget {
final data;
AnotherScreen(this.data);
#override
void build(BuildContext context) {
if (data == 'firstType') {
return widgetForDataWithFirstType(data);
}
return widgetForDatawithSecondType(data);
}
}
And move to this screen:
Navigator.push(
context,
MaterialPageRoute(builder: (context) => AnotherScreen(data)),
);
Where data - parameter that you need to receive to another screen.
You can read more about how to work with navigation in Official Documentation.

Nested Flutter Futurebuilder is not updating the view

I have a Stateful widget that I am displaying inside a stateless widget. I have reused this Widget in several parts of my application, where it worked.
The widget looks a somethin like this
class InnerWidget extends StatefulWidget {
// private keys for objects in the remote dataahase
List<int> privateKeys;
const InnerWidgt({
Key key,
this.privateKeys,
}) : super(key: key);
#override
_InnerWidgetState createState() => _InnerWidgetState();
}
class _InnerWidgetState extends State<InnerWidget> {
// data retrieved from the database
Future<List<SomeClass>> data;
#override
void initState() {
this.data = this.fetchData(widget.privateKeys);
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<SomeClass>> (
future: this.data,
builder: (BuildContext contest, AsyncSnapshot<List<SomeClass>> snapshot) {
if(snapshot.hasData){
// return a widget (a list of names in this case)
}
return Text('Loading...');
}
);
}
Future<List<SomeClass>> fetchData(List<int> pKeys){
// fetches the JSON objects from the server and returns them as instances
}
}
The widget is created and given a list of keys, it makes a call to the remote API to get some objects and displays them in a list. I thought it might be nice that this widget is kind of handling itself, but maybe it's bad design?
What happens, is that the fetchData() method is never called and the "Loading..." is displayed forever. However snapshot.hasData does return true, so I'm thinking this is some kind of repainting issue.
The view I am talking about, where this is used is a detail view, which you navigate to from a list (ListView). I am using this exact same InnerWidget in the the elements inside the ListView, where it works perfectly fine. But when I click on the item to navigate to the detail page, the same InnerWidget does not work. (I know fetching the same data twice is not good, I have that in the back of my head to change it, and there's already a Cache underneath). Maybe this has something to do with the preserved state in the ElementTree? That because I am using the same InnerWidgit, it's preserving its state and not rerendering?
I hope I phrased my question well enough, I did not want to throw hundreds of lines of code at you, that's why created this minimal example of the widget at least. I can add the list and navigation part too if needed, but maybe this is something, that somebody that has worked more with Flutter, knows right away, I'm only 3 weeks in.

I am losing stream data when navigating to another screen

I am new to Dart/Flutter and after "attending" a Udemy course,
everything has been going well.
Until now ;-)
As in the sample application in the Udemy course i am using the BLOC pattern.
Like this:
class App extends StatelessWidget {
Widget build(context) {
return AppBlocProvider(
child: MaterialApp(
(See "AppBlocProvider" which I later on use to get the "AppBloc")
The App as well as all the screens are StatelessWidget's.
The AppBlocProvider extends the InheritedWidget.
Like this:
class AppBlocProvider extends InheritedWidget {
final AppBloc bloc;
AppBlocProvider({Key key, Widget child})
: bloc = AppBloc(),
super(key: key, child: child);
bool updateShouldNotify(_) => true;
static AppBloc of(BuildContext context) {
return (context.inheritFromWidgetOfExactType(AppBlocProvider) as AppBlocProvider).bloc;
}
}
The AppBlocProvider provides an "AppBloc" containing two further bloc's to separate the different data a bit.
Like this:
class AppBloc {
//Variables holding the continuous state
Locale appLocale;
final UserBloc userBloc;
final GroupsBloc groupsBlock;
In my application I have a "GroupSearchScreen" with just one entry field, where you can enter a fragment of a group name. When clicking a button, a REST API call is done and list of group names is returned.
As in the sample application, I put the data in a stream too.
In the sample application the data fetching and putting it in the stream is done in the bloc itself.
On the next line, the screen that uses the data, is created.
Like this:
//Collecting data and putting it in the stream
storiesBloc.fetchTopIds();
//Creating a screen ths shows a list
return NewsList();
In my case however, there are two major differences:
After collecting the data in the GroupSearchScreen, I call/create the GroupsListScreen, where the list of groups shall be shown, using regular routing.
Like this:
//Add data to stream ("changeGroupList" privides the add function of the stream!)
appBloc.groupsBlock.changeGroupList(groups);
//Call/create screen to show list of groups
Navigator.pushNamed(context, '/groups_list');
In the GroupsListScreen, that is created, I fetch the bloc.
Like this:
Widget build(context) {
final AppBloc appBloc = AppBlocProvider.of(context);
These are the routes:
Route routes(RouteSettings settings) {
switch (settings.name) {
case '/':
return createLoginScreen();
case '/search_group':
return createSearchGroupScreen();
case '/groups_list':
return createGroupsListScreen();
default:
return null;
}
}//routes
And "/groups_list" points to this function:
Route createSearchGroupScreen() {
return MaterialPageRoute(
builder: (context) {
//Do we need a DashboardScreen BLOC?
//final storiesBloc = StoriesProvider.of(context);
//storiesBloc.fetchTopIds();
return GroupSearchScreen();
}
);
}
As you can see, the "AppBlocProvider" is only used once.
(I ran into that problem too ;-)
Now to the problem:
When the GroupsListScreen starts rendering the list, the stream/snapshot has no data!
(See "if (!snapshot.hasData)" )
Widget buildList(AppBloc appBloc) {
return StreamBuilder(
stream: appBloc.groupsBlock.groups,
builder: (context, AsyncSnapshot<List<Map<String, dynamic>>>snapshot) {
if (!snapshot.hasData) {
In order to test if all data in the bloc gets lost, I tried not to put the data in the stream directly, but in a member variable (in the bloc!).
In GroupSearchScreen I put the json data in a member variable in the bloc.
Now, just before the GroupsListScreen starts rendering the list, I take the data (json) out of the bloc, put it in the stream, which still resides in the bloc, and everything works fine!
The snapshot has data...
Like this (in the GroupsListScreen):
//Add data to Stream
appBloc.groupsBlock.changeGroupList(appBloc.groupsBlock.groupSearchResult);
Why on earth is the stream "losing" its data on the way from "GroupSearchScreen" to "GroupsListScreen" when the ordinary member variable is not? Both reside in the same bloc!
At the start of the build method of the GroupsListScreen, I have a print statement.
Hence I can see that GroupsListScreen is built twice.
That should be normal, but could that be the reason for not finding data in the stream?
Is the Stream listened on twice?
Widget buildList(AppBloc appBloc) {
return StreamBuilder(
stream: appBloc.groupsBlock.groups,
I tried to explain my problem this way, not providing tons of code.
But I don't know if it's enough to give a hint where I can continue to search...
Update 16.04.2019 - SOLUTION:
I built up my first app using another app seen in a Udemy course...
The most important difference between "his" app and mine is that he creates the Widget that listens to the stream and then adds data to the stream.
I add data to the stream and then navigate to the Widget that shows the data.
Unfortunately I used an RX-Dart "PublishSubject." If you listen to that one you will get all the data put in the stream starting at that time you started listening!
An RX-Dart "BehaviorSubject" however, will also give you the last data, just before you started listening.
And that's the behavior I needed here:
Put data on stream
Create Widget and start listening
I can encourage all Flutter newbies to read both of these very good tutorials:
https://medium.com/flutter-community/reactive-programming-streams-bloc-6f0d2bd2d248
https://www.didierboelens.com/2018/12/reactive-programming---streams---bloc---practical-use-cases/
In the first one, both of the streams mentioned, are explained very well.