Yogesh Bendkoli
Yogesh Bendkoli

Reputation: 1

Flutter Stepper Form with Data Passing Across Pages

Stepper Form with Data Passing Across Pages

This is an explanation of a stepper form with multiple pages, where data entered on earlier pages is passed to the final review page.

Step 1: Personal Details

On this page, the user provides personal details:

The data is saved temporarily to be used on subsequent pages.

Step 2: Profile Picture Upload

Here, the user uploads a profile picture. This data doesn't directly affect the review page but is stored with the user's profile.

Step 3: Workspace Details

The user provides workspace information, such as:

This data is stored for display on the final review page.

Step 4: Review Form

On the review page, the user sees a summary of the data entered:

The user can review and submit or make changes.

class OnboardingScreen extends StatefulWidget {
  final OnboardingApiService? apiService;
  
  const OnboardingScreen({super.key, required this.apiService,});

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

class _OnboardingScreenState extends State<OnboardingScreen> {
  
  final PageController _pageController = PageController();
  final _formKey = GlobalKey<FormState>();
  final TextEditingController firstNameController = TextEditingController();
  final TextEditingController lastNameController = TextEditingController();
  final TextEditingController workspaceController = TextEditingController();
  final TextEditingController companyNameController = TextEditingController();
  final TextEditingController phoneNumberController = TextEditingController();
  final TextEditingController positionController = TextEditingController();

  String? _selectedTeamSize;
  String? selectedCountry;
  String? selectedLanguage;
  String? selectedRole;
  File? _imageFile;
  String? _profilePictureBase64="";

  int _currentStep = 0;
  final List<Step> _steps = [];
  
  final List<bool> _stepRequiresValidation = [true, false, true, true];


  @override
  void initState() {
    super.initState();
    _steps.addAll([
      Step(
        title: const Text('Step 1 of 4'),
        content: _personalInfoForm(),
      ),
      Step(
        title: const Text('Step 2 of 4'),
        content: _profilePictureForm(),
      ),
      Step(
        title: const Text('Step 3 of 4'),
        content: _workspaceDetailsForm(),
      ),
      Step(
        title: const Text('Step 4 of 4'),
        content: _reviewForm(),
      ),
    ]);
    fetchOnboarding();
  }

  // Function to pick an image from the gallery
  Future<void> _pickImage() async {
    final ImagePicker picker = ImagePicker();
    final pickedFile = await picker.pickImage(source: ImageSource.gallery); setState(() { _imageFile = File(pickedFile?.path ?? ''); });
   
  }

  /// For Step Forward
  void _nextStep() {
    
    if ( !_stepRequiresValidation[_currentStep] || _formKey.currentState != null && _formKey.currentState!.validate()) {
      if (_currentStep < _steps.length - 1) {
        setState(() {
          _currentStep++;
        });
        _pageController.nextPage(
            duration: const Duration(milliseconds: 300), curve: Curves.ease);
      } else {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(
            backgroundColor: Colors.red,
            content: Text('Please complete all required fields'),
          ),
        );
      }
    }
  }

  /// For Step Backward
  void _previousStep() {
    if (_currentStep > 0) {
      setState(() {
        _currentStep--;
      });
      _pageController.previousPage(
          duration: const Duration(milliseconds: 300), curve: Curves.ease);
    }
  }

  double _getProgress() {
    return (_currentStep + 1) / _steps.length;
  }

  Future<void> fetchOnboarding() async {
    try {
      OnboardingApiService apiService = OnboardingApiService();
      OnboardingModel? data =
          await apiService.fetchOnboardingData(widget.userToken);

      if (data != null) {
        setState(() {
          firstNameController.text = data.firstName;
          lastNameController.text = data.lastName;
          phoneNumberController.text = data.number;
          selectedRole = data.role;
          selectedCountry = data.country;
          selectedLanguage = data.language;
          companyNameController.text = data.companyName;
          _selectedTeamSize = data.teamSize.toString();
          positionController.text = data.position;
        });
      }
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Error fetching data: $e')),
      );
    }
  }

  Future<void> _submitForm() async {
    if (_formKey.currentState != null && _formKey.currentState!.validate()) {
      await submitOnboarding();
    } else {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          backgroundColor: Colors.red,
          content: Text('Please complete all required fields'),
        ),
      );
    }
  }

  Future<void> submitOnboarding() async {
    OnboardingApiService apiService = OnboardingApiService();

    OnboardingModel data = OnboardingModel(
      firstName: firstNameController.toString(),
      lastName: lastNameController.toString(),
      profilePicture: _imageFile?.path ?? '',
      role: selectedRole ?? '',
      number: phoneNumberController.toString(),
      country: selectedCountry ?? '',
      language: selectedLanguage ?? '',
      companyName: companyNameController.toString(),
      teamSize: int.tryParse(_selectedTeamSize ?? '0') ?? 0,
      position: positionController.toString(),
    );

    bool success = await apiService.submitOnboardingData(widget.userToken, data);

    if(success == true){
      await TokenStorage.saveUserState(isLoggedIn: true, isOnboardingCompleted: true);
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(
          backgroundColor: Colors.green,
          content: Text('Onboarding completed successfully!')),
        );

        Navigator.of(context).pushReplacement(
          MaterialPageRoute(builder: (ctx) => const HomeScreen()),
        );
    } else {
      ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('Onboarding submission failed.')),
    );
    }
  }

  Widget _personalInfoForm() {

    return SingleChildScrollView(
      child: Center(
        child: Form(
          key: _formKey,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              mainAxisSize: MainAxisSize.min,
              children: [
               
                Container(
                  padding: const EdgeInsets.all(10.0),
                  vertical: 10),
                  decoration: BoxDecoration(
                    color: Colors.white,
                    borderRadius: BorderRadius.circular(10),
                    boxShadow: [
                      BoxShadow(
                        color: Colors.grey.withOpacity(0.2),
                        spreadRadius: 4,
                        blurRadius: 8,
                        offset: const Offset(0, 4),
                      ),
                    ],
                  ),
                  child:  Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        const Text(
                          'First Name',
                          style: TextStyle(fontWeight: FontWeight.w500),
                        ),
                        TextFormField(
                          autovalidateMode: AutovalidateMode.onUserInteraction,
                          controller: firstNameController,
                          ),
                          validator: (value) {
                            if (value == null || value.isEmpty) {
                              return 'First name is required';
                            }
                            if (value.length < 2) {
                              return 'First name must be at least 2 characters';
                            }
                            return null;
                          },
                        ),
                        
                        const Text(
                          'Last Name',
                          style: TextStyle(fontWeight: FontWeight.w500),
                        ),
                       
                        TextFormField(
                          controller: lastNameController,
                          autovalidateMode: AutovalidateMode.onUserInteraction,
                          validator: (value) {
                            if (value == null || value.isEmpty) {
                              return 'Last name is required';
                            }
                            if (value.length < 2) {
                              return 'Last name must be at least 2 characters';
                            }
                            return null;
                          },
                        ),
                       
                        const Text(
                          'Country',
                          style: TextStyle(fontWeight: FontWeight.w500),
                        ),
                        DropdownButtonFormField<String>(
                          
                          autovalidateMode: AutovalidateMode.onUserInteraction,
                          value: selectedCountry,
                          hint: const Text('Select your country'),
                          items: <String>['India', 'Usa', 'Brazil', 'Russia']
                              .map((String value) {
                            return DropdownMenuItem<String>(
                              value: value,
                              child: Text(value),
                            );
                          }).toList(),
                          onChanged: (String? newValue) {
                            setState(() {
                              selectedCountry = newValue;
                            });
                          },
                          validator: (value) =>
                              value == null ? 'Please select a country' : null,
                        ),
                        const SizedBox(height: 15),
                        const Text(
                          'Language',
                          style: TextStyle(fontWeight: FontWeight.w500),
                        ),
                        DropdownButtonFormField<String>(
                          decoration: InputDecoration(
                            border: OutlineInputBorder(
                              borderRadius: BorderRadius.circular(11.0),
                            ),
                          ),
                          value: selectedLanguage,
                          hint: const Text('Select your language'),
                          items: <String>['Marathi', 'Hindi', 'English']
                              .map((String value) {
                            return DropdownMenuItem<String>(
                              value: value,
                              child: Text(value),
                            );
                          }).toList(),
                          onChanged: (String? newValue) {
                            setState(() {
                              selectedLanguage = newValue;
                            });
                          },
                          validator: (value) =>
                              value == null ? 'Please select a language' : null,
                        ),
                        
                        const Text(
                          'Phone Number (Optional)',
                          style: TextStyle(fontWeight: FontWeight.w500),
                        ),
                        TextFormField(
                          controller: phoneNumberController,
                          autovalidateMode: AutovalidateMode.onUserInteraction,
                          keyboardType: TextInputType.number,
                          validator: (value) {
                            if (value == null || value.isEmpty) {
                              return 'Phone number is required';
                            }
                            if (value.length < 10) {
                              return 'Please enter 10 digit number';
                            }
                            return null;
                          },
                        ),

                        const Text(
                          'Role',
                          style: TextStyle(fontWeight: FontWeight.w500),
                        ),
                        DropdownButtonFormField<String>(
                          decoration: InputDecoration(
                            border: OutlineInputBorder(
                              borderRadius: BorderRadius.circular(11.0),
                            ),
                          ),
                          value: selectedRole,
                          hint: const Text('Select your role'),
                          items: <String>[
                            'Project Owner', 
                            'Project Designer (Architect/ Interior)', 
                            'Project Consultant',
                            ]
                              .map((String value) {
                            return DropdownMenuItem<String>(
                              value: value,
                              child: Text(value),
                            );
                          }).toList(),
                          onChanged: (String? newValue) {
                            setState(() {
                              selectedLanguage = newValue;
                            });
                          },
                          validator: (value) =>
                              value == null ? 'Please select a language' : null,
                        ),
                        
                        SizedBox(
                          child: ElevatedButton(
                            onPressed: _nextStep,
                            style: ElevatedButton.styleFrom(               
                              backgroundColor: const Color(0xFF673ab7),
                            ),
                            child: const Text(
                              'Continue',
                              style: TextStyle(color: Colors.white),
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
               ), // end of outer Column
              ],
            ),
          ),
        ),
      ),
    );
  }
  ////---------------------------WorkspaceDetails ----------------------

  Widget _workspaceDetailsForm() {
   
    return SingleChildScrollView(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Form(
            key: _formKey,
            child:Center(
                child: Container(
                  padding: const EdgeInsets.all(10.0),
                  decoration: BoxDecoration(
                    color: Colors.white,
                    borderRadius: BorderRadius.circular(10),
                    boxShadow: [
                      BoxShadow(
                        color: Colors.grey.withOpacity(0.2),
                        spreadRadius: 4,
                        blurRadius: 8,
                        offset: const Offset(0, 4),
                      ),
                    ],
                  ),
                  child: Column( 
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const SizedBox(height: 30),
                      const Center(
                        child: Text(
                          'Workspace Details',
                          textAlign: TextAlign.center,
                          style: TextStyle(
                            fontSize: 24,
                            fontWeight: FontWeight.bold,
                            color: Color(0xFF673ab7),
                          ),
                        ),
                      ),
                      const Center(
                        child: Text(
                          'Tell us about your work environment',
                          textAlign: TextAlign.center,
                          style: TextStyle(
                            fontSize: 16,
                            color: Colors.grey,
                          ),
                        ),
                      ), 
      
                      const Text(
                        'Company Name',
                        style: TextStyle(fontWeight: FontWeight.w500),
                      ),
                      TextFormField(
                        controller: companyNameController,
                        decoration: const InputDecoration(
                          hintText: 'Acme Inc',
                          border: OutlineInputBorder(),
                        ),
                        validator: (value) {
                          if (value == null || value.isEmpty) {
                            return 'Company Name is required';
                          }
                          if (value.length < 2) {
                            return 'Company name must be at least 2 characters';
                          }
                          return null;
                        },
                      ),
      
                      const Text(
                        'Team Size',
                        style: TextStyle(fontWeight: FontWeight.w500),
                      ),
                      DropdownButtonFormField<String>(
                        decoration: const InputDecoration(
                          border: OutlineInputBorder(),
                        ),
                        value: _selectedTeamSize,
                        hint: const Text('Select your team size'),
                        items: <String>[
                          'Just me',
                          '2-5 people',
                          '6-10 people',
                          '11-20 people',
                          '21-50 people',
                          '50+ people'
                        ].map((String value) {
                          return DropdownMenuItem<String>(
                            value: value,
                            child: Text(value),
                          );
                        }).toList(),
                        onChanged: (String? newValue) {
                          setState(() {
                            _selectedTeamSize = newValue;
                          });
                        },
                        validator: (value) =>
                            value == null ? 'Please select a team size' : null,
                      ),
      
                      const Text(
                        'Your Position',
                        style: TextStyle(fontWeight: FontWeight.w500),
                      ),
      
                      TextFormField(
                        controller: positionController,
                        validator: (value) {
                          if (value == null || value.isEmpty) {
                            return 'Your Position is required';
                          }
                          if (value.length < 2) {
                            return 'Position must be at least 2 characters';
                          }
                          return null;
                        },
                      ),
      
                      Row(
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: [
                          ElevatedButton(
                            onPressed: _previousStep,
                            style: ElevatedButton.styleFrom(
                                padding: const EdgeInsets.symmetric(
                                    horizontal: 25, vertical: 15),
                                shape: RoundedRectangleBorder(
                                    borderRadius: BorderRadius.circular(9.0))),
                            child: const Text(
                              'Back',
                              style: TextStyle(color: Colors.black),
                            ),
                          ),
                          ElevatedButton(
                            onPressed: _nextStep,
                            style: ElevatedButton.styleFrom(
                                padding: const EdgeInsets.symmetric(
                                    horizontal: 25, vertical: 15),
                                backgroundColor: const Color(0xFF673ab7),
                                shape: RoundedRectangleBorder(
                                    borderRadius: BorderRadius.circular(9.0))),
                            child: const Text(
                              'Continue',
                              style: TextStyle(color: Colors.white),
                            ),
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _reviewForm() {

    //return ReviewForm();
    return SingleChildScrollView(
      child: Center(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  Container(
                    decoration: BoxDecoration(
                        color: const Color.fromARGB(255, 243, 242, 242),
                        borderRadius: BorderRadius.circular(10.0)),
                    child: Padding(
                      padding: const EdgeInsets.all(10.0),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          const Text(
                            'Personal Details',
                            style: TextStyle(
                              fontSize: 20,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                          const SizedBox(height: 15),
                          
                          Text('First Name: ${firstNameController.text}'),
                          const SizedBox(height: 5),
                              Text('Last Name:  ${lastNameController.value.text.toString()}'),
                          const SizedBox(height: 5),
                          Text('Company Name: ${companyNameController.text}'),
                          const SizedBox(height: 5),
                          Text('Your Position: ${positionController.text}'),

                          
                        ],
                      ),
                    ),
                  ),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceAround,
                    children: [
                      ElevatedButton(
                          onPressed: _previousStep,
                          style: ElevatedButton.styleFrom(
                              padding: const EdgeInsets.symmetric(
                                  horizontal: 25, vertical: 15),
                              shape: RoundedRectangleBorder(
                                  borderRadius: BorderRadius.circular(9.0))),
                          child: const Text(
                            'Back',
                            style: TextStyle(color: Colors.black),
                          )),
                      ElevatedButton(
                        onPressed: () {
                          _submitForm();
                        },
                        style: ElevatedButton.styleFrom(
                          padding: const EdgeInsets.symmetric(
                              horizontal: 25, vertical: 15),
                          backgroundColor: const Color(0xFF673ab7),
                          shape: RoundedRectangleBorder(
                            borderRadius: BorderRadius.circular(9.0),
                          ),
                        ),
                        child: const Text(
                          'Complete Setup',
                          style: TextStyle(color: Colors.white),
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            const SizedBox(height: 45),
            const Text(
              'Welcome Aboard',
              textAlign: TextAlign.center,
              style: TextStyle(
                fontSize: 28,
                fontWeight: FontWeight.bold,
                color: Color(0xFF673ab7),
              ),
            ),
            const SizedBox(height: 8),
            const Text(
              "Let's personalize your experience",
              textAlign: TextAlign.center,
              style: TextStyle(
                fontSize: 16,
                color: Colors.grey,
              ),
            ),
            const SizedBox(height: 16),
            LinearProgressIndicator(
              minHeight: 10,
              value: _getProgress(),
              backgroundColor: Colors.grey[300],
              color: const Color(0xFF673ab7),
              borderRadius: BorderRadius.circular(11.0),
            ),
           
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text(
                  'Step ${_currentStep + 1}/4',
                  textAlign: TextAlign.center,
                  style: const TextStyle(
                    fontSize: 16,
                    color: Colors.black,
                  ),
                ),
                Text(
                  '${(_getProgress() * 100).toInt()}% completed',
                  textAlign: TextAlign.center,
                  style: const TextStyle(
                    fontSize: 16,
                    color: Colors.black,
                  ),
                ),
              ],
            ),
            const SizedBox(height: 8),
            Flexible(
              child: PageView(
                controller: _pageController,
                physics: const NeverScrollableScrollPhysics(),
                children: _steps.map((step) => step.content).toList(),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Upvotes: 0

Views: 22

Answers (0)

Related Questions