How to save user login info in iOS Flutter App

670 Views Asked by At

I'm having trouble saving user credentials on ios version of my flutter app.

I have implemented the AutofillGroup and autofillHints also I added the associate domains, here you can see the code and the Xcode setup:

import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_fimber/flutter_fimber.dart';
import 'package:get_it/get_it.dart';
import 'package:medfyle/bloc/auth/auth_form_cubit.dart';
import 'package:medfyle/presentation/theme/theme.dart';

import '../../../bloc/user/user_bloc.dart';
import '../../../navigator.dart';
import '../../component/mixin/custom_tracker.dart';
import '../../component/mixin/loading.dart';
import '../../component/mixin/manage_error.dart';
import '../../component/widget/appbar/base_app_bar.dart';
import '../../component/widget/custom_button.dart';

class LoginPage extends StatefulWidget {
  static const routeName = '/login_page';

  const LoginPage({Key? key}) : super(key: key);

  @override
  State<LoginPage> createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage>
    with Loading, ManageError, CustomTracker {
  @override
  Widget build(BuildContext context) {
    trackScreenWithName(traceName: LoginPage.routeName);
    return BlocProvider<AuthFormBlocCubit>(
      create: (BuildContext context) => GetIt.instance.get<AuthFormBlocCubit>(),
      child: Scaffold(
          appBar: BaseAppBar(label: "auth.login.title".tr()),
          body: SafeArea(
            child: BlocListener<UserBloc, UserState>(
              listener: (context, state) {
                if (state is UserIsLoading) {
                  showLoader(context);
                }
                if (state is UserLoginError) {
                  manageError(context, state.error,
                      showDialog: state.showDialog,
                      showSnack: false,
                      dialogCb: hideLoader(context));
                  hideLoader(context);
                  Fimber.e(state.error.toString());
                }
                if (state is UserIsLogged) {
                  N.goToHome(context);
                }
              },
              child: Padding(
                padding: kDefaultPadding,
                child: BlocBuilder<AuthFormBlocCubit, AuthFormState>(
                    builder: (context, state) {
                  return AutofillGroup(
                    child: Column(
                      children: <Widget>[
                        TextFormField(
                          onChanged: (v) {
                            context.read<AuthFormBlocCubit>().changeEmail(v);
                          },
                          autofillHints: const [
                            AutofillHints.email,
                            AutofillHints.username,
                            AutofillHints.newUsername
                          ],
                          keyboardType: TextInputType.emailAddress,
                          decoration: InputDecoration(
                            hintText: "auth.form.email".tr(),
                            labelText: "auth.form.email".tr(),
                          ),
                        ),
                        const SizedBox(height: 16),
                        TextFormField(
                          onChanged: (v) {
                            context.read<AuthFormBlocCubit>().changePassword(v);
                          },
                          keyboardType: TextInputType.visiblePassword,
                          obscureText: true,
                          autofillHints: const [
                            AutofillHints.password,
                            AutofillHints.newPassword
                          ],
                          decoration: InputDecoration(
                            hintText: "auth.form.password".tr(),
                            labelText: "auth.form.password".tr(),
                          ),
                        ),
                        const SizedBox(height: 8),
                        const Spacer(),
                        CustomButton.fullWidth(
                          label: "auth.login.btn_enter".tr(),
                          onPressed: state.validLoginForm
                              ? () {
                                  context.read<UserBloc>().add(Login(
                                      state.email!.trim(),
                                      state.password!.trim()));
                                  TextInput.finishAutofillContext();
                                }
                              : null,
                        ),
                      ],
                    ),
                  );
                }),
              ),
            ),
          )),
    );
  }
}

Flutter doctor:

[✓] Flutter (Channel stable, 3.7.12, on macOS 13.0 22A380 darwin-arm64, locale en-US)
[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 14.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2021.2)
[✓] VS Code (version 1.80.2)
[✓] Connected device (3 available)
[✓] HTTP Host Availability

This is what I'm getting:

This is what I'm getting

This is my xcode setup

I'm missing the "save" popup after logging in: Missing popup

Also, how can I make the suggestion work like this (instead of simple "Password" suggestion which I'm getting at the moment): Desired suggestion format

2

There are 2 best solutions below

1
Marko Marinkovic On

Try following this format. Replace your TextFormFields with TextFields. You need to pass controllers to each TextField.

@override
  Widget build(BuildContext context) {
    return AutofillGroup(
      child: Column(
        children: <Widget>[
          TextField(controller: username, autofillHints: [AutofillHints.username]),
          Checkbox(
            value: isNewUser,
            onChanged: (bool newValue) {
              setState(() { isNewUser = newValue; });
            },
          ),
          if (isNewUser) TextField(controller: newPassword, autofillHints: [AutofillHints.newPassword]),
          if (isNewUser) TextField(controller: repeatNewPassword, autofillHints: [AutofillHints.newPassword]),
          if (!isNewUser) TextField(controller: password, autofillHints: [AutofillHints.password]),
        ],
      ),
    );
  }

For more info see the documentation.

2
Afzal K. On

1st, add the keychain_service package to your pubspec.yaml file:

dependencies:
  keychain_service: ^1.0.0

2nd, import the package in the widget where the user login info should be saved:

import 'package:keychain_service/keychain_service.dart';

3rd, on the login page, after the successful login, save the data using the KeychainService instance:

final keychain = KeychainService();
await keychain.write(key: 'email', value: email);
await keychain.write(key: 'password', value: password);

4th, this will save the email and password in the keychain. To retrieve the data, use the KeychainService instance again:

final keychain = KeychainService();
String email = await keychain.read(key: 'email');
String password = await keychain.read(key: 'password');

5th, for this to work, the user must have their iCloud keychain enabled on