Nodejs Flutter App Update

Nodejs Flutter App Update

    This tutorial would post here about the update of the app. Since the app is huge in terms of time and content, we decided to make a dedicate tutorial here about. 

    Here we post any problems that you are facing and their solutions.

    4:18:58 time line missing user token

    On youtube tutorial at the above timeline, it's missing how to generate the user token. Here's the video for it.

    Message schema

    During the video uploading, we have missed recording about message.js. Here is the code below

    const mongoose = require("mongoose");
    
    const messageSchema = mongoose.Schema(
        {
            sender: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
            content: { type: String, trim: true },
            receiver: { type: String, trim: true },
            chat: { type: mongoose.Schema.Types.ObjectId, ref: "Chat" },
            readBy: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }],
        },
        { timestamps: true }
    );
    
    module.exports = mongoose.model("Message", messageSchema);

    Image Url

    2:27:27 I cannot see the image URL completely and it is not there in the app constants.dart file either

    You may use any random url from the internet.

    Railway issue connecting with github

    Unfortunately, the attached GitHub account does not provide enough data on who you are

    In order for one to host a server on railway, railway app should be have access to the repo that you want to host. When you create a new app and configuring where to get the content authorize railway to access that repo alternatively you can give railway to all the repositories that you have and you can be able to search through your repositories and pick the one you want to use.

    Code for Drawer Item

    This code should be in lib->views->ui folder.

    import 'package:flutter/material.dart';
    import 'package:flutter_screenutil/flutter_screenutil.dart';
    import 'package:flutter_vector_icons/flutter_vector_icons.dart';
    import 'package:flutter_zoom_drawer/flutter_zoom_drawer.dart';
    import 'package:jobfinder/controllers/zoom_provider.dart';
    import 'package:jobfinder/views/common/exports.dart';
    
    import 'package:provider/provider.dart';
    
    class MainScreen extends StatefulWidget {
      const MainScreen({Key? key}) : super(key: key);
    
      @override
      State<MainScreen> createState() => _MainScreenState();
    }
    
    class _MainScreenState extends State<MainScreen> {
      @override
      Widget build(BuildContext context) {
        return Consumer<ZoomNotifier>(
          builder: (context, zoomNotifier, child) {
            return ZoomDrawer(
              menuScreen: DrawerScreen(
                indexSetter: (index) {
                  zoomNotifier.currentIndex = index;
                },
              ),
              mainScreen: currentScreen(),
              borderRadius: 30,
              showShadow: true,
              angle: 0.0,
              slideWidth: 250,
              menuBackgroundColor: Color(kLightBlue.value),
            );
          },
        );
      }
    
      Widget currentScreen() {
        var zoomNotifier = Provider.of<ZoomNotifier>(context);
        switch (zoomNotifier.currentIndex) {
          case 0:
            return const HomePage();
         
          case 1:
            return const ChatsPage();
          case 2:
            return const BookMarkPage();
          case 3:
            return const DeviceManagement();
          case 4:
            return const ProfilePage();
    
          default:
            return const HomePage();
        }
      }
    }
    
    class DrawerScreen extends StatefulWidget {
      final ValueSetter indexSetter;
      const DrawerScreen({Key? key, required this.indexSetter}) : super(key: key);
    
      @override
      State<DrawerScreen> createState() => _DrawerScreenState();
    }
    
    class _DrawerScreenState extends State<DrawerScreen> {
      @override
      Widget build(BuildContext context) {
        var zoomNotifier = Provider.of<ZoomNotifier>(context);
        return GestureDetector(
          onDoubleTap: () {
            ZoomDrawer.of(context)!.toggle();
          },
          child: Scaffold(
            backgroundColor: Color(kLightBlue.value),
            body: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                drawerItems(
                    AntDesign.home,
                    "Home",
                    0,
                    zoomNotifier.currentIndex == 0
                        ? Color(kLight.value)
                        : Color(kLightGrey.value),
                    zoomNotifier.currentIndex == 0
                        ? Color(kLight.value)
                        : Color(kLightGrey.value)),
                const HeightSpacer(size: 20),
               
                drawerItems(
                    Ionicons.ios_chatbubble_outline,
                    "Chat",
                    1,
                    zoomNotifier.currentIndex == 1
                        ? Color(kLight.value)
                        : Color(kDarkGrey.value),
                    zoomNotifier.currentIndex == 1
                        ? Color(kLight.value)
                        : Color(kDarkGrey.value)),
                const HeightSpacer(size: 20),
                drawerItems(
                    Fontisto.bookmark,
                    "Bookmarks",
                    2,
                    zoomNotifier.currentIndex == 2
                        ? Color(kLight.value)
                        : Color(kDarkGrey.value),
                    zoomNotifier.currentIndex == 2
                        ? Color(kLight.value)
                        : Color(kDarkGrey.value)),
                 const HeightSpacer(size: 20),
    
                drawerItems(
                    MaterialCommunityIcons.devices,
                    "Device Mgmt",
                    3,
                    zoomNotifier.currentIndex == 3
                        ? Color(kLight.value)
                        : Color(kDarkGrey.value),
                    zoomNotifier.currentIndex == 3
                        ? Color(kLight.value)
                        : Color(kDarkGrey.value)),
    
                const HeightSpacer(size: 20),
    
                drawerItems(
                     FontAwesome5Regular.user_circle,
                    "Profile",
                    4, 
                    zoomNotifier.currentIndex == 4
                        ? Color(kLight.value)
                        : Color(kDarkGrey.value),
                    zoomNotifier.currentIndex == 4
                        ? Color(kLight.value)
                        : Color(kDarkGrey.value)),
              ],
            ),
          ),
        );
      }
    
      Widget drawerItems(
          IconData icon, String text, int index, Color color, Color txtcolor) {
        return GestureDetector(
          onTap: () {
            widget.indexSetter(index);
          },
          child: Container(
            margin: const EdgeInsets.only(left: 20, bottom: 12),
            child: Row(
              children: [
                Icon(
                  icon,
                  color: color,
                ),
                const SizedBox(
                  width: 12,
                ),
                ReusableText(
                  text: text,
                  style: appstyle(12, color, FontWeight.bold),
                ),
              ],
            ),
          ),
        );
      }
    
    }
    

    JWT_SEC

    Here you may set up your .env file like below

    PORT = 5002
    MONGO_URL = mongodb+srv://jobhubdb:Thehub2023@jobhubdb.1eeaug2.mongodb.net/jobhubdb
    JWT_SEC = jobhub2023
    SECRET = jobhub2023 

    Make sure you set up your port according to your needs. You may also need to set up rest based on your environment and set up.

    Update User

    The video is missing udpate user code. Make sure you see the code carefully and use the part as you need. 

    class PersonalDetails extends StatefulWidget {
      const PersonalDetails({super.key});
    
      @override
      State<PersonalDetails> createState() => _PersonalDetailsState();
    }
    
    class _PersonalDetailsState extends State<PersonalDetails> {
      TextEditingController phone = TextEditingController();
      TextEditingController location = TextEditingController();
      TextEditingController skill0 = TextEditingController();
      TextEditingController skill1 = TextEditingController();
      TextEditingController skill2 = TextEditingController();
      TextEditingController skill3 = TextEditingController();
      TextEditingController skill4 = TextEditingController();
    
      @override
      void dispose() {
        phone.dispose();
        location.dispose();
        skill0.dispose();
        skill1.dispose();
        skill2.dispose();
        skill3.dispose();
        skill4.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(body: Consumer<LoginNotifier>(
          builder: (context, loginNotifier, child) {
            return Form(
              key: loginNotifier.profileFormKey,
              child: ListView(
                padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 60.h),
                children: [
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      ReusableText(
                          text: "Personal Details",
                          style: appstyle(35, Color(kDark.value), FontWeight.bold)),
                      Consumer<ImageUpoader>(
                        builder: (context, imageUploader, child) {
                          return imageUploader.imageFil.isEmpty
                              ? GestureDetector(
                                  onTap: () {
                                    imageUploader.pickImage();
                                  },
                                  child: CircleAvatar(
                                    backgroundColor: Color(kLightBlue.value),
                                    // backgroundImage: ,
                                    child: const Center(
                                      child: Icon(Icons.photo_filter_rounded),
                                    ),
                                  ),
                                )
                              : GestureDetector(
                                  onTap: () {
                                    imageUploader.imageFil.clear();
                                    setState(() {});
                                  },
                                  child: CircleAvatar(
                                    backgroundColor: Color(kLightBlue.value),
                                    backgroundImage:
                                        FileImage(File(imageUploader.imageFil[0])),
                                  ),
                                );
                        },
                      )
                    ],
                  ),
                  const HeightSpacer(size: 20),
                  Form(
                      child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      CustomTextField(
                        controller: location,
                        hintText: "Location",
                        keyboardType: TextInputType.text,
                        validator: (location) {
                          if (location!.isEmpty) {
                            return "Please enter a valid location";
                          } else {
                            return null;
                          }
                        },
                      ),
                      const HeightSpacer(size: 10),
                      CustomTextField(
                        controller: phone,
                        hintText: "Phone Number",
                        keyboardType: TextInputType.phone,
                        validator: (phone) {
                          if (phone!.isEmpty) {
                            return "Please enter a valid phone";
                          } else {
                            return null;
                          }
                        },
                      ),
                      const HeightSpacer(size: 10),
                      ReusableText(
                          text: "Professional Skills",
                          style: appstyle(30, Color(kDark.value), FontWeight.bold)),
                      const HeightSpacer(size: 10),
                      CustomTextField(
                        controller: skill0,
                        hintText: "Proffessional Skills",
                        keyboardType: TextInputType.text,
                        validator: (skill0) {
                          if (skill0!.isEmpty) {
                            return "Please enter a valid phone";
                          } else {
                            return null;
                          }
                        },
                      ),
                      const HeightSpacer(size: 10),
                      CustomTextField(
                        controller: skill1,
                        hintText: "Proffessional Skills",
                        keyboardType: TextInputType.text,
                        validator: (skill1) {
                          if (skill1!.isEmpty) {
                            return "Please enter a valid phone";
                          } else {
                            return null;
                          }
                        },
                      ),
                      const HeightSpacer(size: 10),
                      CustomTextField(
                        controller: skill2,
                        hintText: "Proffessional Skills",
                        keyboardType: TextInputType.text,
                        validator: (skill2) {
                          if (skill2!.isEmpty) {
                            return "Please enter a valid phone";
                          } else {
                            return null;
                          }
                        },
                      ),
                      const HeightSpacer(size: 10),
                      CustomTextField(
                        controller: skill3,
                        hintText: "Proffessional Skills",
                        keyboardType: TextInputType.text,
                        validator: (skill3) {
                          if (skill3!.isEmpty) {
                            return "Please enter a valid phone";
                          } else {
                            return null;
                          }
                        },
                      ),
                      const HeightSpacer(size: 10),
                      CustomTextField(
                        controller: skill4,
                        hintText: "Proffessional Skills",
                        keyboardType: TextInputType.text,
                        validator: (skill4) {
                          if (skill4!.isEmpty) {
                            return "Please enter a valid phone";
                          } else {
                            return null;
                          }
                        },
                      ),
                      const HeightSpacer(size: 20),
                      Consumer<ImageUpoader>(
                        builder: (context, imageUploada, child) {
                          return CustomButton(
                              onTap: () {
                                if (imageUploada.imageFil.isEmpty &&
                                    imageUploada.imageUrl == null) {
                                  Get.snackbar("Image Missing",
                                      "Please upload an image to proceed",
                                      colorText: Color(kLight.value),
                                      backgroundColor: Color(kLightBlue.value),
                                      icon: const Icon(Icons.add_alert));
                                } else {
                                  ProfileUpdateReq model = ProfileUpdateReq(
                                      location: location.text,
                                      phone: phone.text,
                                      profile: imageUploada.imageUrl.toString(),
                                      skills: [
                                        skill0.text,
                                        skill1.text,
                                        skill2.text,
                                        skill3.text,
                                        skill4.text,
                                      ]);
    
                                  loginNotifier.updateProfile(model);
                                }
                              },
                              text: "Update Profile");
                        },
                      )
                    ],
                  ))
                ],
              ),
            );
          },
        ));
      }
    }
    

    Auth Helper

    Part of the auth helper code is missing in the video. You may check out the code and use some code as you need.

    class AuthHelper {
      static var client = https.Client();
    
      static Future<bool> login(LoginModel model) async {
        Map<String, String> requestHeaders = {'Content-Type': 'application/json'};
    
        var url = Uri.https(Config.apiUrl, Config.loginUrl);
        var response = await client.post(
          url,
          headers: requestHeaders,
          body: jsonEncode(model),
        );
    
        if (response.statusCode == 200) {
          final SharedPreferences prefs = await SharedPreferences.getInstance();
    
          String token = loginResponseModelFromJson(response.body).userToken;
          String userId = loginResponseModelFromJson(response.body).id;
          String profile = loginResponseModelFromJson(response.body).profile;
    
          await prefs.setString('token', token);
          await prefs.setString('userId', userId);
          await prefs.setString('profile', profile);
          await prefs.setBool('loggedIn', true);
    
          return true;
        } else {
          return false;
        }
      }
    
      static Future<bool> signup(SignupModel model) async {
        Map<String, String> requestHeaders = {'Content-Type': 'application/json'};
    
        var url = Uri.https(Config.apiUrl, Config.signupUrl);
        var response = await client.post(
          url,
          headers: requestHeaders,
          body: jsonEncode(model),
        );
    
        if (response.statusCode == 201) {
          return true;
        } else {
          return false;
        }
      }
    
      static Future<bool> updateProfile(ProfileUpdateReq model) async {
        final SharedPreferences prefs = await SharedPreferences.getInstance();
        String? token = prefs.getString('token');
    
        Map<String, String> requestHeaders = {
          'Content-Type': 'application/json',
          'token': 'Bearer $token'
        };
    
        var url = Uri.https(Config.apiUrl, Config.profileUrl);
        var response = await client.put(
          url,
          headers: requestHeaders,
          body: jsonEncode(model),
        );
    
        if (response.statusCode == 200) {
          return true;
        } else {
          return false;
        }
      }
    
      static Future<ProfileRes> getProfile() async {
        final SharedPreferences prefs = await SharedPreferences.getInstance();
        String? token = prefs.getString('token');
    
        Map<String, String> requestHeaders = {
          'Content-Type': 'application/json',
          'token': 'Bearer $token'
        };
    
        var url = Uri.https(Config.apiUrl, Config.profileUrl);
        var response = await client.get(
          url,
          headers: requestHeaders,
        );
    
        if (response.statusCode == 200) {
          var profile = profileResFromJson(response.body);
          return profile;
        } else {
          throw Exception("Failed to get the profile");
        }
      }
    }
    

    Login Provider

    Part of the code is missing in the video. It's about login provider. Take the code as you need.

    class LoginNotifier extends ChangeNotifier {
      bool _obscureText = true;
    
      bool get obscureText => _obscureText;
    
      set obscureText(bool newState) {
        _obscureText = newState;
        notifyListeners();
      }
    
      bool _firstTime = true;
    
      bool get firstTime => _firstTime;
    
      set firstTime(bool newState) {
        _firstTime = newState;
        notifyListeners();
      }
    
      bool? _entrypoint;
    
      bool get entrypoint => _entrypoint ?? false;
    
      set entrypoint(bool newState) {
        _entrypoint = newState;
        notifyListeners();
      }
    
      bool? _loggedIn;
    
      bool get loggedIn => _loggedIn ?? false;
    
      set loggedIn(bool newState) {
        _loggedIn = newState;
        notifyListeners();
      }
    
      getPrefs() async {
        final SharedPreferences prefs = await SharedPreferences.getInstance();
    
        entrypoint = prefs.getBool('entrypoint') ?? false;
        loggedIn = prefs.getBool('loggedIn') ?? false;
      }
    
      final loginFormKey = GlobalKey<FormState>();
      final profileFormKey = GlobalKey<FormState>();
    
      bool validateAndSave() {
        final form = loginFormKey.currentState;
    
        if (form!.validate()) {
          form.save();
          return true;
        } else {
          return false;
        }
      }
    
      bool profileValidation() {
        final form = profileFormKey.currentState;
    
        if (form!.validate()) {
          form.save();
          return true;
        } else {
          return false;
        }
      }
    
      userLogin(LoginModel model) {
        AuthHelper.login(model).then((response) {
          if (response && firstTime) {
            Get.off(() => const PersonalDetails());
          } else if (response && !firstTime) {
            Get.off(() => const MainScreen());
          } else if (!response) {
            Get.snackbar("Sign Failed", "Please Check your credentials",
                colorText: Color(kLight.value),
                backgroundColor: Colors.red,
                icon: const Icon(Icons.add_alert));
          }
        });
      }
    
      logout() async {
        final SharedPreferences prefs = await SharedPreferences.getInstance();
        await prefs.setBool('loggedIn', false);
        await prefs.remove('token');
        _firstTime = false;
      }
    
      updateProfile(ProfileUpdateReq model) async {
        AuthHelper.updateProfile(model).then((response) {
          if (response) {
            Get.snackbar("Profile Update", "Enjoy your search for a job",
                colorText: Color(kLight.value),
                backgroundColor: Color(kLightBlue.value),
                icon: const Icon(Icons.add_alert));
    
            Future.delayed(const Duration(seconds: 3)).then((value) {
              Get.offAll(() => const MainScreen());
            });
          } else {
            Get.snackbar("Updating Failed", "Please try again",
                colorText: Color(kLight.value),
                backgroundColor: Color(kOrange.value),
                icon: const Icon(Icons.add_alert));
          }
        });
      }
    }