Flutter use variable created inside Future builder outside of the build method,

I don’t fully understand the reason to have a global TextController (and I don’t think its a good idea either) but using Riverpod it would look somethink like this:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class AccountData {
  final String firstName;
  final String lastName;
  final String phoneNumber;
  
  AccountData({
    required this.firstName,
    required this.lastName,
    required this.phoneNumber,
  });
  
  factory AccountData.fromJson(Map<String, dynamic> json) {
    return AccountData(
      firstName: json['firstName'],
      lastName: json['lastName'],
      phoneNumber: json['phoneNumber'],
    );
  }
}

Future<Map<String, dynamic>> fetchAccountData() async {
  final response = {
    'firstName': 'Name',
    'lastName': 'LastName',
    'phoneNumber': '98786758690',
  };
  return Future.delayed(const Duration(seconds: 3), () => response);
}

final futureData = FutureProvider<AccountData>((_) async {
  final data = await fetchAccountData();
  return AccountData.fromJson(data);
});
  
final firstNameController = ChangeNotifierProvider<TextEditingController>((ref) {
  final controller = TextEditingController();
  ref.listen<AsyncValue<AccountData>>(futureData, (_, curr) {
    curr.whenOrNull(
      data: (d) => controller.text = d.firstName,
    );
  });
  
  return controller;
});

final lastNameController = ChangeNotifierProvider<TextEditingController>((ref) {
  final controller = TextEditingController();
  ref.listen<AsyncValue<AccountData>>(futureData, (_, curr) {
    curr.whenOrNull(
      data: (d) => controller.text = d.lastName,
    );
  });
  
  return controller;
});

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp();
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: MyWidget(),
        ),
      ),
    );
  }
}

class MyWidget extends ConsumerWidget {
  
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final firstController = ref.watch(firstNameController.notifier);
    final lastcontroller = ref.watch(lastNameController.notifier);
    return ref.watch(futureData).when(
      loading: () => const CircularProgressIndicator(),
      error: (e, _) => TextButton(
        child: Text('$e'),
        onPressed: () => ref.refresh(futureData),
      ),
      data: (_) {
        return Column(
          children: [
            TextField(
              controller: firstController,
            ),
            TextField(
              controller: lastcontroller,
            ),
          ]
        );
      }
    );
  }
}

Now following Hazar Belge using a late initializer is a good idea, without state managament the idea would be simply use a FutureBuilder and then pass down the data to an StatefulWidget which initialize the TextEditingControllers with that data, and again using didUpdateWidget, all of it without depending of an external package (the bigger your app you would need to use more packages to help you develop fast but I would recommend try with the basic to grasp all you can do with the framework)

Leave a Comment