Rajdeep
Rajdeep

Reputation: 2482

Flutter: Change ListTile based on Firestore query

I'm trying to configure the appearance of a list tile based on a firestore query. So I have a set of list tiles as such:

enter image description here

What I want to achieve, when this page is loaded, the left side of the tile marks if a user has completed that particular lesson. So an 'x' means he has not but a 'tick' would mean that he has. Currently it is all hard coded to be 'x':

    ListTile makeLessonListTile(Lesson lesson) => ListTile(
          contentPadding:
              EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0),
          leading: Container(
            padding: EdgeInsets.only(right: 12.0),
            decoration: new BoxDecoration(
                border: new Border(
                    right: new BorderSide(width: 1.0, color: Colors.white24))),
            child: IconButton(
              icon: Icon(Icons.close, color: Colors.white), // Hardcoded to be 'x'
            ),
          ),
          title: Text(
            lesson.title,
            style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
          ),

          subtitle: Row(
            children: <Widget>[
              Expanded(
                  flex: 1,
                  child: Container(
                    child: LinearProgressIndicator(
                        backgroundColor: Color.fromRGBO(209, 224, 224, 0.2),
                        value: lesson.indicatorValue,
                        valueColor: AlwaysStoppedAnimation(Colors.green)),
                  )),
              Expanded(
                flex: 4,
                child: Padding(
                    padding: EdgeInsets.only(left: 10.0),
                    child: Text(lesson.level,
                        style: TextStyle(color: Colors.white))),
              )
            ],
          ),
          trailing:
              Icon(Icons.keyboard_arrow_right, color: Colors.white, size: 30.0),
          onTap: () {
            Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => QuizPage(
                    lesson: lesson, 
                    auth: widget.auth, 
                    onSignedOut: widget.onSignedOut, 
                    userId: widget.userId,
                  )
                )
            );
          },
        );

    Card makeLessonCard(Lesson lesson) => Card(
          elevation: 8.0,
          margin: new EdgeInsets.symmetric(horizontal: 10.0, vertical: 6.0),
          child: Container(
            decoration: BoxDecoration(color: Color.fromRGBO(64, 75, 96, .9)),
            child: makeLessonListTile(lesson),
          ),
        );

    // the scaffold body
    final makeLessonBody = Container(
      child: ListView.builder(
        scrollDirection: Axis.vertical,
        shrinkWrap: true,
        itemCount: lessons.length,
        itemBuilder: (BuildContext context, int index) {
          return makeLessonCard(lessons[index]);
        },
      ),
    );

I know how to do the query, I'm just not sure where to do it such that when the page loads, it is automatically updated to ticks and crosses based on the user results.

The query would be :

    FirebaseUser user = await widget.auth.getCurrentUser();
    Firestore.instance
      .collection('Users')
      .document(user.email)
      .collection('Quiz Data')
      .document('Courses')
      .collection(lesson.abbr.toString().substring(5))
      .document(lesson.title)
      .get()
      .then((DocumentSnapshot ds) {
        if (ds.exists) {
          if (ds['pass']) return true;
        }
        return false;
    });

Base Auth Class I use for Authentication:

import 'dart:async';
import 'package:firebase_auth/firebase_auth.dart';

abstract class BaseAuth {
  Future<String> signIn(String email, String password);

  Future<String> signUp(String email, String password);

  Future<FirebaseUser> getCurrentUser();

  Future<void> sendEmailVerification();

  Future<void> signOut();

  Future<bool> isEmailVerified();
}

class Auth implements BaseAuth {
  final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;

  Future<String> signIn(String email, String password) async {
    FirebaseUser user = await _firebaseAuth.signInWithEmailAndPassword(
        email: email, password: password);
    return user.uid;
  }

  Future<String> signUp(String email, String password) async {
    FirebaseUser user = await _firebaseAuth.createUserWithEmailAndPassword(
        email: email, password: password);
    return user.uid;
  }

  Future<FirebaseUser> getCurrentUser() async {
    FirebaseUser user = await _firebaseAuth.currentUser();
    return user;
  }

  Future<void> signOut() async {
    return _firebaseAuth.signOut();
  }

  Future<void> sendEmailVerification() async {
    FirebaseUser user = await _firebaseAuth.currentUser();
    user.sendEmailVerification();
  }

  Future<bool> isEmailVerified() async {
    FirebaseUser user = await _firebaseAuth.currentUser();
    return user.isEmailVerified;
  }

}

Update - What I tried to do:

Use ternary operator:

class _NavigationPageState extends State<NavigationPage> {
  . . . // omitted code
  bool passed;

  @override
  void initState() {
    passed = false;
    . . . // omitted code
    super.initState();
  }

  checkUserPassedLesson (Lesson lesson) async {
    FirebaseUser user = await widget.auth.getCurrentUser();
    Firestore.instance
      .collection('Users')
      .document(user.email)
      .collection('Quiz Data')
      .document('Courses')
      .collection(lesson.abbr.toString().substring(5))
      .document(lesson.title)
      .get()
      .then((DocumentSnapshot ds) {
        if (ds.exists) {
          if (ds['pass'])  {
            passed = true;
            return;
          }
        }
        passed = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    // for lesson page
    ListTile makeLessonListTile(Lesson lesson) => ListTile(
          contentPadding:
              EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0),
          leading: Container(
            padding: EdgeInsets.only(right: 12.0),
            decoration: new BoxDecoration(
                border: new Border(
                    right: new BorderSide(width: 1.0, color: Colors.white24))),
            child: IconButton(
              icon: passed ? Icon(Icons.done, color: Colors.white) : Icon(Icons.close, color: Colors.white),
            ),
          ),
          title: Text(
            lesson.title,
            style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
          ),

          subtitle: Row(
            children: <Widget>[
              Expanded(
                  flex: 1,
                  child: Container(
                    child: LinearProgressIndicator(
                        backgroundColor: Color.fromRGBO(209, 224, 224, 0.2),
                        value: lesson.indicatorValue,
                        valueColor: AlwaysStoppedAnimation(Colors.green)),
                  )),
              Expanded(
                flex: 4,
                child: Padding(
                    padding: EdgeInsets.only(left: 10.0),
                    child: Text(lesson.level,
                        style: TextStyle(color: Colors.white))),
              )
            ],
          ),
          trailing:
              Icon(Icons.keyboard_arrow_right, color: Colors.white, size: 30.0),
          onTap: () {
            Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => QuizPage(
                    lesson: lesson, 
                    auth: widget.auth, 
                    onSignedOut: widget.onSignedOut, 
                    userId: widget.userId,
                  )
                )
            );
          },
        );


    Card makeLessonCard(Lesson lesson) => Card(
          elevation: 8.0,
          margin: new EdgeInsets.symmetric(horizontal: 10.0, vertical: 6.0),
          child: Container(
            decoration: BoxDecoration(color: Color.fromRGBO(64, 75, 96, .9)),
            child: makeLessonListTile(lesson),
          ),
        );

    // query here and route accordingly
    final makeLessonBody = Container(
      child: ListView.builder(
        scrollDirection: Axis.vertical,
        shrinkWrap: true,
        itemCount: lessons.length,
        itemBuilder: (BuildContext context, int index) {
          checkUserPassedLesson(lessons[index]);
          return makeLessonCard(lessons[index]);
        },
      ),
    );
    . . . // omitted code

    return Scaffold(
      backgroundColor: Color.fromRGBO(58, 66, 86, 1.0),
      appBar: topAppBar,
      body: makeLessonBody,
      bottomNavigationBar: makeBottom,
    );
 }
}

Put Query in init:

class _NavigationPageState extends State<NavigationPage> {
  ... // omitted code
  bool passed = false;
  Container makeLessonBody;

  @override
  void initState() {
    ... // omitted code

    // for lesson page
    ListTile makeLessonListTile(Lesson lesson) => ListTile(
          contentPadding:
              EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0),
          leading: Container(
            padding: EdgeInsets.only(right: 12.0),
            decoration: new BoxDecoration(
                border: new Border(
                    right: new BorderSide(width: 1.0, color: Colors.white24))),
            child: IconButton(
              icon: passed ? Icon(Icons.done, color: Colors.white) : Icon(Icons.close, color: Colors.white),
            ),
          ),
          title: Text(
            lesson.title,
            style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
          ),

          subtitle: Row(
            children: <Widget>[
              Expanded(
                  flex: 1,
                  child: Container(
                    child: LinearProgressIndicator(
                        backgroundColor: Color.fromRGBO(209, 224, 224, 0.2),
                        value: lesson.indicatorValue,
                        valueColor: AlwaysStoppedAnimation(Colors.green)),
                  )),
              Expanded(
                flex: 4,
                child: Padding(
                    padding: EdgeInsets.only(left: 10.0),
                    child: Text(lesson.level,
                        style: TextStyle(color: Colors.white))),
              )
            ],
          ),
          trailing:
              Icon(Icons.keyboard_arrow_right, color: Colors.white, size: 30.0),
          onTap: () {
            Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => QuizPage(
                    lesson: lesson, 
                    auth: widget.auth, 
                    onSignedOut: widget.onSignedOut, 
                    userId: widget.userId,
                  )
                )
            );
          },
        );

    Card makeLessonCard(Lesson lesson) => Card(
          elevation: 8.0,
          margin: new EdgeInsets.symmetric(horizontal: 10.0, vertical: 6.0),
          child: Container(
            decoration: BoxDecoration(color: Color.fromRGBO(64, 75, 96, .9)),
            child: makeLessonListTile(lesson),
          ),
        );

    makeLessonBody = Container(
      child: ListView.builder(
        scrollDirection: Axis.vertical,
        shrinkWrap: true,
        itemCount: lessons.length,
        itemBuilder: (BuildContext context, int index) {
          checkUserPassedLesson(lessons[index]);
          debugPrint(passed.toString());
          return makeLessonCard(lessons[index]);
        },
      ),
    );
    super.initState();
  }

  void checkUserPassedLesson (Lesson lesson) async {
    FirebaseUser user = await widget.auth.getCurrentUser();
    Firestore.instance
      .collection('Users')
      .document(user.email)
      .collection('Quiz Data')
      .document('Courses')
      .collection(lesson.abbr.toString().substring(5))
      .document(lesson.title)
      .get()
      .then((DocumentSnapshot ds) {
        if (ds.exists) {
          if (ds['pass'])  {
            setState(() {
              debugPrint(ds['pass'].toString());
              passed = true;              
            });
          }
        } else {
        setState(() {
          passed = false;          
        });}
    });
  }

      ... // omitted code


  @override
  Widget build(BuildContext context) {
      ... // omitted code

      return Scaffold(
      backgroundColor: Color.fromRGBO(58, 66, 86, 1.0),
      appBar: topAppBar,
      body: makeLessonBody,
      bottomNavigationBar: makeBottom,
    );
  }
}

Latest Update: Since the above methods were not working out, I tried to use a FutureBuilder using the ternary operator and it worked.

Full code:

class NavigationPage extends StatefulWidget {

  NavigationPage({Key key, this.auth, this.userId, this.onSignedOut, this.userEmail}) : super(key: key);

  final BaseAuth auth;
  final VoidCallback onSignedOut;
  final String userId;
  final String userEmail;

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

class _NavigationPageState extends State<NavigationPage> {
  List courses;
  List lessons;
  String title;
  TabStatus tabStatus;
  bool showLessons;
  bool _isLoading;

  @override
  void initState() {
    title = COURSE_PAGE_TITLE;      
    _isLoading = false;
    tabStatus = TabStatus.COURSE;
    showLessons = false;
    courses = StaticMethods.getCourses();
    // temp value
    lessons = StaticMethods.getLessons(Abbr.P01);
    super.initState();
  }

  _signOut() async {
    setState(() {
     _isLoading = true; 
    });
    try {
      await widget.auth.signOut();
      widget.onSignedOut();
      setState(() {
        _isLoading = false; 
      });
    } catch (e) {
      setState(() {
       _isLoading = false; 
      });
      print(e);
    }
  }

  Widget _showLoading(){
    if (_isLoading) {
      return Center(
        child: ColorLoader5(
          dotOneColor: Colors.white24,
          dotTwoColor: Colors.white70,
          dotThreeColor: Colors.white,
          dotType: DotType.circle,
          dotIcon: Icon(Icons.adjust),
          duration: Duration(seconds: 1),      
        )
      );
    } 
    return Container(height: 0.0, width: 0.0,);
  }

  Widget _showLoadingTile() {
    return Center (
      child: Container(
        height: MediaQuery.of(context).size.height/10,
        width: MediaQuery.of(context).size.width/2,
        child: ColorLoader5(
          dotOneColor: Colors.white24,
          dotTwoColor: Colors.white70,
          dotThreeColor: Colors.white,
          dotType: DotType.circle,
          dotIcon: Icon(Icons.adjust),
          duration: Duration(seconds: 1),      
        ),
      ) 
    );
  }

  Future<bool> checkUserPassedLesson (Lesson lesson) async {
    bool passed;
    await Firestore.instance
      .collection('Users')
      .document(widget.userEmail)
      .collection('Quiz Data')
      .document('Courses')
      .collection(lesson.abbr.toString().substring(5))
      .document(lesson.title)
      .get()
      .then((DocumentSnapshot ds) {
        debugPrint("querying");
        if (ds.exists) {
          debugPrint('exists');
          passed = ds['pass'];
        } else passed = false;
    });
    return passed;
  }

  @override
  Widget build(BuildContext context) {     

    // for lesson page
    ListTile makeLessonListTile(Lesson lesson, bool passed) => ListTile(
          contentPadding:
              EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0),
          leading: Container(
            padding: EdgeInsets.only(right: 12.0),
            decoration: new BoxDecoration(
                border: new Border(
                    right: new BorderSide(width: 1.0, color: Colors.white24))),
            child: IconButton(
              icon: passed ? Icon(Icons.done, color: Colors.white) : Icon(Icons.close, color: Colors.white),
            ),
          ),
          title: Text(
            lesson.title,
            style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
          ),

          subtitle: Row(
            children: <Widget>[
              Expanded(
                  flex: 1,
                  child: Container(
                    child: LinearProgressIndicator(
                        backgroundColor: Color.fromRGBO(209, 224, 224, 0.2),
                        value: lesson.indicatorValue,
                        valueColor: AlwaysStoppedAnimation(Colors.green)),
                  )),
              Expanded(
                flex: 4,
                child: Padding(
                    padding: EdgeInsets.only(left: 10.0),
                    child: Text(lesson.level,
                        style: TextStyle(color: Colors.white))),
              )
            ],
          ),
          trailing:
              Icon(Icons.keyboard_arrow_right, color: Colors.white, size: 30.0),
          onTap: () {
            Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => QuizPage(
                    lesson: lesson, 
                    auth: widget.auth, 
                    onSignedOut: widget.onSignedOut, 
                    userId: widget.userId,
                  )
                )
            );
          },
        );

    Card makeLessonCard(Lesson lesson, bool passed) => Card(
          elevation: 8.0,
          margin: new EdgeInsets.symmetric(horizontal: 10.0, vertical: 6.0),
          child: Container(
            decoration: BoxDecoration(color: Color.fromRGBO(64, 75, 96, .9)),
            child: makeLessonListTile(lesson, passed),
          ),
        );

    final makeLessonBody = Container(
      child: ListView.builder(
        scrollDirection: Axis.vertical,
        shrinkWrap: true,
        itemCount: lessons.length,
        itemBuilder: (BuildContext context, int index) {
          return FutureBuilder<bool>(
            future: checkUserPassedLesson(lessons[index]),
            builder: (BuildContext context, 
                      AsyncSnapshot<bool> snapshot) {
              if (snapshot.hasError) return new Text('${snapshot.error}');
              switch (snapshot.connectionState) {
                case ConnectionState.waiting:
                  return Center(child: _showLoadingTile());
                default:
                  return makeLessonCard(lessons[index], snapshot.data);
              }
            },
          );
        },
      ),
    );   
          return Scaffold(
            backgroundColor: Color.fromRGBO(58, 66, 86, 1.0),
            appBar: topAppBar, // omitted code
            body: makeLessonBody,
            bottomNavigationBar: makeBottom, // omitted code
          );
 }
}

Upvotes: 2

Views: 1985

Answers (1)

Swift
Swift

Reputation: 3410

You don't have to repeat the function just to change the icon. Use a ternary operator instead (C# example but the concept is the same).

bool passed = checkUserPassedLesson(lesson);
...

IconButton(
  icon: passed ? Icon(Icons.done, color: Colors.white) : Icon(Icons.close, color: Colors.white),
),

If passed is true it uses the done icon and close icon if its not.

Upvotes: 1

Related Questions