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:

  • First Name (e.g., “Tom”)

  • Last Name (e.g., “Dsouza”)

  • Select Country, Select Language, and Select Role

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:

  • Company Name (e.g., “Google”)

  • Select Team Size

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:

  • Personal Details: First Name: Tom, Last Name: Dsouza, Country, Language, Role.

  • Workspace Details: Company Name: Google, Team Size.

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(),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

New contributor

Yogesh Bendkoli is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật