Nodejs Flutter App Update

Created At: 2023-06-05 23:36:01 Updated At: 2023-07-27 18:06:19

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));
      }
    });
  }
}

Comment

  • S
    SherriBo

    2024-03-12 14:03:27

    Hi, I love this video and the information is great. I have learned alot but the stuff is all over the place. It's very confusing because the code that I downloaded (paid full version) doesn't match the code that is in the video so I don't know what is correct so I think I've ended up with half what is in the video and half what I downloaded. I'm now half way through the second video and I can't get the profile to save. The information above is also confusing. My drawer seems to be working fine but it is where it was in the downloaded version under views/common/drawer, not where it says it should be above which is views/ui/drawer. The Drawer code above also starts with MainScreen. How does that make any sense? On several occasions the video is cut and the screen displayed on the simulator changes, so something was done but I have no idea what. At this point, I'm not sure what I am supposed to follow. I'm getting a response of {} after I update the profile then I get these in my print statements, then an error: flutter: this is the error {} flutter: this is the imageURL https://firebasestorage.googleapis.com/v0/b/medmed-a8f74.appspot.com/o/medmed%2Fd286aa20-e06a-11ee-8e79-59ef4249e473.jpg?alt=media&token=b1ead146-7cad-4a6a-8e37-470b3857176f flutter: is it getting past the imageFile.path [GETX] GOING TO ROUTE /MainScreen flutter: http://medmedbackend-production.up.railway.app/api/jobs ======== Exception caught by image resource service ================================================ The following ArgumentError was thrown resolving an image codec: Invalid argument(s): No host specified in URI file:/// When the exception was thrown, this was the stack: #0 _HttpClient._openUrl (dart:_http/http_impl.dart:2749:9) I think it has something to do with this statement you have on ~row72 // Fix: Image not loading, somewhere // the userData.profile is being set to // the string 'null', this shouldn't be // the case, make it nullable rather. // so for now, I'm just checking for 'n // ull', You should rather make profile // nullable. I didn't have time to go // change the profile everywhere and // correct the backend code or wherever // it was being set as null // child: UserData!.profile != 'null' // ? Image.asset( // 'assets/images/user.png', // ) However, I get the same response from Update User in Postman, just the empty brackets {} I'm sure I am doing something wrong because your coding skills are alot better than mine but I am trying to learn so that is to be expected. 1. Is the final code that I downloaded the correct code? I have modified it several times as I am going through the video so I may need to start with a fresh Flutter project LIB. It honestly seems like it is because I have had to make changes while going through your training video where the downloaded code was different. 2. If the final code that is downloaded does NOT reflect the final state of the code, why not? How can I learn when I don't know which code I'm supposed to follow? Anyway, I may have just misunderstood something because, like I said, your coding is a much higher level than mine. I am currently at the part of the video where the video is cut and then your profile is displayed. My simulator profile page as the error 'Error type 'Null' is not a subtype of type 'String' (somewhere around Video 2 - 2:16 I have no idea what is causing it. Thanks for putting this together, it's really great, I'm just confused in some areas. Thanks also for any assistance you can give me. -Sherri Boley

Add Reviews