Slava Lucker
Slava Lucker

Reputation: 23

Make cards with texts and buttons dynamically

I'm making Notes app. I made cards with text and buttons dynamically (Create by clicking the button). But I have problem with Changing Text on CURRENT card. For example, I have 3 cards with own texts and buttons and I want to change text on 2nd card but text is changing on the 3rd card. How can I solve this problem?

3 cards with texts and buttons

Change Text Page

In the past, I've tried making list to collect texts, but i dont know how to identify current card.

full main.dart

    import 'package:flutter/material.dart';

    import './changeTextPage.dart';

    int count = 0;
    String titlecard = '';
    String textcard = '';

    void main() => runApp(MyApp());

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Notes',
          theme: ThemeData(
            primarySwatch: Colors.deepPurple
          ),
          home: HomePage(title: 'Notes',),
        );
      }
    }

    class HomePage extends StatefulWidget {
      HomePage({Key key, this.title}) : super(key: key);
      final title;
      @override
      HomePageState createState() => HomePageState();
    }

    class HomePageState extends State<HomePage> {

      @override
      Widget build(BuildContext context) {
        List<Widget> cards = new List.generate(count, (int i) => new MyCard());

        return Scaffold(
          appBar: AppBar(
            title: Text('Notes'),
          ),
          body: LayoutBuilder(
            builder: (context, constraint) {
            return Column(
              children: <Widget>[
                Container(
                  height: 650.0,
                  child: new ListView(
                    children: cards,
                    scrollDirection: Axis.vertical,
                  ),
                ),
              ],
            );
          }
          ),
          floatingActionButton: FloatingActionButton(
            child: Icon(Icons.add),
            onPressed: () {
              setState(() {
                Navigator.push(context, MaterialPageRoute(
                    builder: (context) => changeText())
                );

              });
            },
          ),
        );
      }
    }

    class MyCard extends StatefulWidget {
      @override
      myCard createState() => myCard();
    }

    class myCard extends State<MyCard> {
      int myCount = count;

      void click() {
        setState(() {
          Navigator.push(context, MaterialPageRoute(
              builder: (context) => setNewText())
          );
        });

      }


      @override
      Widget build(BuildContext context) {
        return Center(
          child: Card(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                  ListTile(
                  leading: Icon(Icons.album),
                  title: Text(titlecard),
                  subtitle: Text(textcard),
                ),
                ButtonTheme.bar( // make buttons use the appropriate styles for cards
                  child: ButtonBar(
                    children: <Widget>[
                      FlatButton(
                        child: const Text('Change Text'),
                        onPressed: click,
                      ),
                      FlatButton(
                        child: const Text('LISTEN'),
                        onPressed: () { /* ... */ },
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
        );
      }


    }

    class setNewText extends StatefulWidget {
      @override
      SetNewText createState() => SetNewText();
    }

    class SetNewText extends State<setNewText> {
      final titleController = TextEditingController();
      final textController = TextEditingController();
      final formkey = GlobalKey<FormState>();

      void _submit() {
        setState(() {
          if (formkey.currentState.validate()) {
            formkey.currentState.save();
            Navigator.pop(context);
            titlecard = titleController.text;
            textcard = textController.text;

          }
        });

      }

      @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
              title: Text('Change Title'),
            ),
            body: Column(
              children: <Widget>[
                Card(
                  child: Padding(
                    padding: EdgeInsets.all(2.0),
                    child: Form(
                      key: formkey,
                      child: Column(
                        children: <Widget>[
                          TextFormField(
                            controller: titleController,
                            decoration: InputDecoration(
                                labelText: 'Title'
                            ),
                            validator: (value) => value.length < 1 ? 'Invalid Title' : null,
                            onSaved: (value) => value = titleController.text,
                          ),
                          TextFormField(
                            controller: textController,
                            decoration: InputDecoration(
                                labelText: 'Text'
                            ),
                            validator: (text) => text.length < 1 ? 'Invalid Text' : null,
                            onSaved: (text) => text = textController.text,
                          )
                        ],
                      ),
                    ),
                  ),
                ),
                FlatButton(
                  textColor: Colors.deepPurple,
                  child: Text('SUBMIT'),
                  onPressed: _submit,
                ),
              ],
            )
        );
      }


    }

changeTextPage.dart

    import 'package:flutter/material.dart';
    import './main.dart';

    class changeText extends StatefulWidget {
      @override
      ChangeText createState() => ChangeText();
    }

    class ChangeText extends State<changeText> {
      myCard s = myCard();
      final titleController = TextEditingController();
      final textController = TextEditingController();
      final formkey = GlobalKey<FormState>();

      void _submit() {
        setState(() {
          if (formkey.currentState.validate()) {
            formkey.currentState.save();
            Navigator.pop(context);
            count++;
            titlecard = titleController.text;
            textcard = textController.text;
          }
        });

      }

      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Change Title'),
          ),
          body: Column(
            children: <Widget>[
              Card(
                child: Padding(
                  padding: EdgeInsets.all(2.0),
                  child: Form(
                    key: formkey,
                    child: Column(
                        children: <Widget>[
                          TextFormField(
                            controller: titleController,
                            decoration: InputDecoration(
                              labelText: 'Title'
                            ),
                            validator: (value) => value.length < 1 ? 'Invalid Title' : null,
                            onSaved: (value) => value = titleController.text,
                          ),
                          TextFormField(
                            controller: textController,
                            decoration: InputDecoration(
                                labelText: 'Text'
                            ),
                            validator: (text) => text.length < 1 ? 'Invalid Text' : null,
                            onSaved: (text) => text = textController.text,
                          )
                        ],
                      ),
                  ),
                  ),
                ),
              FlatButton(
                textColor: Colors.deepPurple,
                child: Text('SUBMIT'),
                onPressed: _submit,
              ),
            ],
          )
        );
      }


    }

Upvotes: 2

Views: 2424

Answers (1)

Albert221
Albert221

Reputation: 7192

Okay, so you happen to make some common mistakes, one of which is critical.

  • most importantly don't use global variables! As you do with count, titlecard and textcard.
  • there is a practice to name stateful widgets with PascalCase and corresponding states just like the widget but prefixed with an underscore (_) to make it private and suffixed by the State word.

The correct approach for this (or one of them) would be to have a widget that would be your screen with a form to edit stuff and it would pop some struct with user values on submit:

class ChangeTextScreen extends StatefulWidget {
  _ChangeTextScreenState createState() => _ChangeTextScreenState();
}

class _ChangeTextScreenState extends State<ChangeTextScreen> {
  void _submit() {
    setState(() {
      formkey.currentState.save();

      Navigator.pop(ChangeTextResult(title: titleController.text, text: textController.text));
    });
  }

  // Rest of your code...
}

class ChangeTextResult {
  final String title;
  final String text;

  ChangeTextResult({@required this.title, @required this.text});
}

You should also have a place where you store your notes in some kind of a list. Your main screen looks like a good place for it. Once your app will be bigger, think about using scoped_model or Redux or something.

So let's add a Note class and a list with your notes to your main screen:

class Note {
    String title;
    String text;

    Note(this.title, this.text);
}

class HomePageState extends State<HomePage> {
  List<Note> _notes = [Note('Test', 'Some test note')];

  @override
  Widget build(BuildContext context) {
    ListView cards = ListView.builder(
        itemCount: _notes.length,
        itemBuilder: (context, index) => MyCard(
            title: _notes[index].title,
            text: _notes[index].text,
            onEdit: (title, text) => setState(() { // We'll get back to that later
                _notes[index].title = title;
                _notes[index].text = text;
            })
        ));
// (...)

Your MyCard widget (try to use better names next time) should contain some kind of information about its content, one of the best approaches would be to pass this info to its constructor, just like that:

class MyCard extends StatefulWidget {
    final String title;
    final String text;
    final Function onEdit;

    MyCard({Key key, @required this.title, @required this.text, @required this.onEdit}) : super(key: key);

    @override
    _MyCardState createState() => _MyCardState();
}

Having this Key parameter is a good practice.

And use those parameters in your _MyCardState class (I renamed it from _myCard):

// (...)
    children: <Widget>[
        ListTile(
        leading: Icon(Icons.album),
        title: Text(widget.title),
        subtitle: Text(widget.text),
    ),
// (...)

Returning to the moment where you open your ChangeTextScreen, you should assign the result of Navigation.push() to a variable. This is your result, you can deal with it (once we check it for null, the user could have returned from this screen and then the result would be null).

void click() {
    setState(() {
        final ChangeTextResult result = Navigator.push(context, MaterialPageRoute(
            builder: (context) => ChangeTextScreen())
        );

        if (result != null) {
            widget.onEdit(result.title, result.text);
        }
    });
}

Do you remember that onEdit parameter (I mentioned it in a comment in the code above)? We call that parameter here.


That's it I think. I could have mixed some concepts of your app, but I think you'll manage to get my point anyways.

I quite rewrote all of your code. I think it will be easier for you to start again from scratch and have those tips in mind. Also, try to Google some similar things (like a simple Todo application) or do Getting started from flutter.io with part two! That should give you a nice idea on how to resolve that common problem in Flutter.

And also, read about good practises in Flutter and Dart. Things like correctly formatting your code are really important.

BTW that's my longest answer on Stack Overflow so far. I hope you'll appreciate that.

Upvotes: 3

Related Questions