Append items dynamically to ListView - mobile

I am new to Dart and Flutter and try to append a new item to my ListView. I created a button that increments this.value but nothing happens. Am I missing an update call on the UI and is this even the correct approach?
Since I return the value of ListView.builder directly to the caller of build I am not sure how to get the list to add more items. Thanks a lot!
class MyList extends State<MyList> {
...
int value = 2;
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: this.value,
itemBuilder: (context, index) => this._buildRow(index)
);
TL;DR: Is calling setState the correct way to trigger an UI update?

Calling setState is the correct way of triggering an UI update.
From the docs:
Calling setState notifies the framework that the internal state of
this object has changed in a way that might impact the user interface
in this subtree, which causes the framework to schedule a build for
this State object.
If you just change the state directly without calling setState, the
framework might not schedule a build and the user interface for this
subtree might not be updated to reflect the new state.
Here is a small example of a ListView with a Button that appends items to it.
import 'package:flutter/material.dart';
void main() => runApp(new MaterialApp(home: MyList()));
class MyList extends StatefulWidget {
#override
_MyListState createState() => _MyListState();
}
class _MyListState extends State<MyList> {
int value = 2;
_addItem() {
setState(() {
value = value + 1;
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text("MyApp"),
),
body: ListView.builder(
itemCount: this.value,
itemBuilder: (context, index) => this._buildRow(index)),
floatingActionButton: FloatingActionButton(
onPressed: _addItem,
child: Icon(Icons.add),
),
);
}
_buildRow(int index) {
return Text("Item " + index.toString());
}
}

Related

Boolean expression must not be null?

Does anyone know why i got the Failed assertion: boolean expression must not be null.
If I had logged in and quit the app and open the app again i should be directly in the homescreen.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
// This widget is the root of your application.
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool userIsLoggedIn = false;
#override
void initState(){
getLoggedInState();
super.initState();
}
getLoggedInState()async{
await HelperFunction.getUserLoggedInSharedPreference().then((value){
setState(() {
userIsLoggedIn = value;
});
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: userIsLoggedIn ? HomeScreen() : CompanyLoadingBar(),
);
}
}
If your 'value' here can become null - you get the error.
To be safe change the line inside setState to:
userIsLoggedIn = value ?? false;
it will set the bool to false if value is null
Your problem is that you are not waiting for your Future to resolve to an actual value.
This line:
userIsLoggedIn = value;
might not have been reached when your build method is called. Because it's async and you don't await it.
You can set a default value to userIsLoggedIn, maybe false, that will solve your immediate problem. But it will not solve your real problem, that your program logic is asynchronous.
You will probably want to build your app with at least one FutureBuilder.
See: What is a Future and how do I use it?
Your issue most likely lies in this function where you're setting userIsLoggedIn to null. Ensure getUserLoggedInSharedPreference actually returns a boolean and not null.
void getLoggedInState() async {
final result = await HelperFunction.getUserLoggedInSharedPreference();
if (result != null) {
setState(() {
userIsLoggedIn = result;
});
} else {
print("result is null");
}
}
You have a global approach error. You combine traditional and async/await methods in this code:
getLoggedInState()async{
await HelperFunction.getUserLoggedInSharedPreference().then((value){
setState(() {
userIsLoggedIn = value;
});
});
}
If you use async/await you should not use then method.
To implement what you want you should use something like this:
...
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: FutureBuilder<bool>(
future: HelperFunction.getUserLoggedInSharedPreference(),
builder: (context,snapshot) {
if (snapshot.hasData) {
// Future is ready. Take data from snapshot
userIsLoggedIn = snapshot.data; // bool value is here
return userIsLoggedIn ? HomeScreen() : CompanyLoadingBar();
} else {
// Show progress while future will be completed
return CircularProgressIndicator();
}
}
),
);
}
...
And also check what value is returned from HelperFunction.getUserLoggedInSharedPreference(). It seems it returns null because I think you have no already saved value. So you need to specify the default value:
final value = await SharedPreferences.getInstance().readBool(name) ?? false;

What is the equivalent for `componentDidMount()` in Flutter

I coming from ReactJS and React Native. I want to try out Flutter. So far I want to have a login screen. Therefore I want to check if a user is already logged in. If so forward to the Home Screen. If not, show the login screen.
In React with TypeScript and Firebase I would to it this way:
interface RootScreenState {
isLoading: boolean;
user: firebase.User | null;
}
class RootScreen extends React.Component<{}, RootScreenState> {
constructor(props) {
super(props);
this.state = {
isLoading: true,
user: null
}
}
componentDidMount() {
// make an async call to some firebase libraries to look for stored user credentials
// if some credentials are found try to login
// of no credentials are found or login fails return null
// otherwise the user is returned
tryToLogin().then((currentUser: firebase.User | null) => {
this.setState({
isLoading: false,
user: currentUser
}).catch(err => { /* do some error handling */});
}
render() {
const { isLoading, user } = this.state;
if(isLoading) return ( /* loading screen */ );
else if(user === null) return ( /* login screen */ );
else return ( /* home screen */ );
}
}
How do I do with Flutter? I could not find anything about an equivalent to compnentDidMount(), should I do it in the constructor? In React this would fail.
use initState in Stateful widget.InitState is called when the stateful widget is first time painted. For ex
class _MyAppState extends State<MyApp> {
Future<Album> futureAlbum;
#override
void initState() {
super.initState();
futureAlbum = fetchAlbum();
}
You can do this in Flutter's initState.
It gets called first when your widget tree is rendered.
Check the code below:
#override
void initState() {
super.initState();
}
For more details on various things in React Native you wished to know in Flutter.
See the link below: It is the official documentation of people coming to Flutter from a React Native background.
Documentation
I hope this helps.
When it comes to request data from the server, you may want to use FutureBuilder directly.
Sometimes widget build will trigger twice if you put states in initial of state improperly.
I prefer to put FutureBuilder into widget build scope and more clean and readable for me.
In your case, as you also work with navigation, I would work with a FutureBuilder. Initialize the framework, look for the user and navigate the user to the proper screen based on the state with initialroute.
If you want it nicer, you can play around with a SplashScreen. A good package you can use is flutter_native_splash. You will also find a full example there.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureBuilder(
// The event you are waiting for (can also be your user object, once loaded)
future: _initializeFrameworks,
builder: (context, snapshot) {
// Show splash screen
if (snapshot.connectionState == ConnectionState.waiting) {
return MaterialApp(home: SplashScreen());
} else {
// Return app and navigate
return MaterialApp(
initialRoute: snapshot.data ? MainScreen.id : WelcomeScreen.id,
routes: {
WelcomeScreen.id: (context) => WelcomeScreen(),
MainScreen.id: (context) => MainScreen(),
},
);
}
},
);
}
}
class Mainscreen extends StatelessWidget {
static const id = "main_screen";
// your widget goes here
}
class WelcomeScreen extends StatelessWidget {
static const id = "welcome_screen";
// your widget goes here
}

How to observe object.property changes on an observable array with mobx

I'm using reactjs and mobx. I have an observable array of Item objects, and I'm trying to display them and show property changes "live" by observing the properties on the objects in the array. The changes are not triggered by a click event on any of the components, but by a response to an API call.
I understand that property changes on objects within the array will not trigger the entire list to re-render (which is good), but I can't get it to re-render the single Item component that should be observing the properties of the Item object.
I have tried a couple methods of getting the Item objects within the array to be observable, but none of these are working for me:
calling 'extendObservable() from the Item's constructor
assigning the props.item to a class member decorated with '#observable'
calling the observable constructor and passing in the item object like this: const item = observable(item)
passing the 'hasUnreadData' field as a separate prop and making that observable via 'observable.box(item.hasUnreadData).
Here's some simplified example code (in typescript):
class Item {
id : string
hasUnreadData : boolean
constructor (data: any) {
this.id = data.id;
// false by default
this.hasUnreadData = data.hasUnreadData;
}
}
#observable items: Item[];
// observes the array and re-renders when items are added/removed (this works)
#observer
class ItemListComponent extends React.Component {
render() {
return (
<List> {
items.map((item: Item, index) => {
<ItemComponent key={item.id} itemModel={item} />
}
}
)
}
}
// should observe the 'hasUnreadData' flag and apply different styles when it re-renders (but this does not work, it only displays the initial state)
#observer
class ItemComponent extends React.Component {
render() {
const item = this.props.item;
return (
<ListItem button divider selected={item.hasUnreadData} />
)
}
}
// imagine this is a promise from an API call
API.fetchData().then((itemId: string) => {
itemToUpdate = items.find(i => i.id === itemId);
itemToUpdate.hasUnreadData = true;
// this does not trigger the ItemComponent to render again as expected.
});
Do I need to clone or otherwise "re-create" the Item object to trigger render? Or am I making come kind of obvious mistake here? Any help appreciated.

Property is always undefined in React component

I have a simple React component that should retrieve some objects from an api and display them on screen. The problem is, the property holding my data always seems to have an undefined value. In C#, I have a Property class:
public class Property
{
public int PropertyID { get; set; }
public string Name { get; set; }
}
My component's parent gets a list of these from an api and passes them in as props.
Here's my component:
export interface IPropertySearchResult {
PropertyID: number,
Name: string
}
export interface IPropertySearchResults {
Results: IPropertySearchResult[]
}
export interface IPropertyWindowProps {
Properties: IPropertySearchResults
}
export interface IPropertyWindowState {
}
export class PropertyWindow extends React.Component<IPropertyWindowProps,
IPropertyWindowState> {
constructor(props: IPropertyWindowProps) {
super(props);
}
render() {
return (
<div >
<ul>
{
this.props.Properties && this.props.Properties.Results ?
this.props.Properties.Results.map((p: IPropertySearchResult, idx: number) =>
<li key={idx}> <PropertyEditor Property={p} /> </li>)
: 'properties null'
}
</ul>
</div>
);
}
}
As shown in the image below, this.props.Properties contains the objects I need but for some reason this.props.Properties.Results is always marked as undefined.
.
Update
I think the problem has something to do with the way I'm reading the data. I have my controller:
[HttpGet("Search")]
public IActionResult GetProperties()
{
var props = new Property[]
{
new Property(){PropertyID=1, Name="default1"},
new Property(){PropertyID=2, Name="default2"},
new Property(){PropertyID=3, Name="default3"},
};
return Ok(new { Properties = props });
}
}
And its client:
export class PropertyReader {
public static search(): Promise<IPropertySearchResults> {
return new Promise<IPropertySearchResults>((resolve, reject) => {
axios.get(`api/Settings/Search`)
.then(res => {
resolve(res.data);
});
});
}
}
Then my component's parent calls the client:
componentDidMount() {
PropertyReader.search()
.then(p => this.setState({ properties: p }));
}
For some reason, it's creating an IPropertySearchResults and putting the data into a dynamically added array rather than into the Results array.
It turns out, this had a really silly solution. The values were getting save to a property with a lower case name. I had to change
export interface IPropertySearchResults {
Results: IPropertySearchResult[]
}
to
export interface IPropertySearchResults {
results: IPropertySearchResult[]
}

Render React component from instantiated React.Component

I have a couple of React components that are all based of the same base class, these component have a couple of properties which I would like to read before I render the component. This has to do with some conditions that are used somewhere else.
Currently I am calling a method, with something like this in my Render function.
public getWidget(): JSX.Element {
let widget = null;
switch (widgetType) {
case 'widget1': {
widgetComponent = new Widget1(props); // private variable in my class
widget = (<Widget1 { ...props } ref = { some-ref });
}
case 'widget2': {
widgetComponent = new Widget2(props); // private variable in my class
widget = (<Widget2 { ...props } ref = { some-ref });
}
}
return widget;
}
This way I can ask the widget some stuff about it's default values and render the widget variable in my Render function, like this:
render() {
const widget = this.getWidget();
const somethingIWantToKnow = this.widgetComponent.someProperty;
return ({ widget });
}
From what I understand, the reference I set for my React Component is only available after I render? Otherwise I could just use that.
I also tried calling this.widgetComponent.render() in my own Render method, but this does not set up the component correctly (probably because of missing componentWillMount and componentDidMount calls.
I just can't believe this is the way to go, is there a way to render from this.widgetComponent in my Render method, or is there a way to get properties from the class behind a JSX.Element?
NULL checks and other stuff is all removed from these code snippets :)
Give your widget a ref,
widget = (<Widget1 { ...props } ref = { widget1 } />);
Then you can access your instantiated component in componentDidMount and use the ref to access the property,
componentDidMount(){
const somethingIWantToKnow = this.widget1.current.someProperty
}

Resources