passing generic type by Function(T) in flutter

quantum apps picture quantum apps · Sep 11, 2019 · Viewed 9.9k times · Source

I'm trying to create a generic consumer widget that facilitates the ViewModel to its child. therefor I have two functions. one that has a function(T) after init of the ViewModel and the other for passing the model to its child Widget.

in the generic class is a child of ChangeNotifier and that works fine until I want to send the T value in the two Functions.

then I get the following errors:

type '(OnBoardingViewModel) => Null' is not a subtype of type '(ChangeNotifier) => void'

and

type '(BuildContext, OnBoardingViewModel, Widget) => Scaffold' is not a subtype of type '(BuildContext, ChangeNotifier, Widget) => Widget'

But when i change the extends type from ChangeNotifier to OnBoardingViewModel, everything works fine.

can someone help me and or explain why this ain't working??

import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:provider/provider.dart';

class StateFullConsumerWidget<T extends ChangeNotifier> extends StatefulWidget{

  StateFullConsumerWidget({@required this.builder,Key key,this.onPostViewModelInit,this.child}) : super(key : key);

  final Widget Function(BuildContext context, ChangeNotifier value, Widget child) builder;
  final Widget child;

  final void Function(T) onPostViewModelInit;

  @override
  _StateFullConsumerWidgetState<T> createState() => _StateFullConsumerWidgetState<T>();
}

class _StateFullConsumerWidgetState<T extends ChangeNotifier> extends State<StateFullConsumerWidget>{
  T _viewModel;
  @override
  void initState() {
    // assign the model once when state is initialised
    _viewModel = GetIt.instance.get<T>();
    widget.onPostViewModelInit(_viewModel);


    super.initState();
  }
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<T>(
      builder: (context) => _viewModel,
      child: Consumer<T>(
        builder: widget.builder,
        child: widget.child,
      ),
    );
  }

}

my widget

StateFullConsumerWidget<OnBoardingViewModel>(
      onPostViewModelInit: (viewModel){
        buildIntroList(viewModel);
        viewModel.maxPages = _introWidgetsList.length;
      },
      builder: (context,viewModel,child) {
        return Scaffold(
          key: widget.scaffoldKey,
          body: SafeArea(
            child: Container(),
            ),
          ),
        );
      },
    );

my ViewModel

import 'package:flutter/material.dart';

class OnBoardingViewModel extends ChangeNotifier{

  OnBoardingViewModel(){

  }
}

Answer

Abion47 picture Abion47 · Sep 11, 2019

The way you are using the generic type T is incomplete. The relationship between the StateFullConsumerWidget and the _StateFullConsumerWidgetState classes as written in your code are such that StateFullConsumerWidget creates its state using the same T type parameter as itself, so the widget knows the state uses the same generic type that it does. From the perspective of _StateFullConsumerWidgetState, though, the class is declared as such:

class _StateFullConsumerWidgetState<T extends ChangeNotifier> 
    extends State<StateFullConsumerWidget>

The problem is the state class is using the general form of StateFullConsumerWidget, so there is no explicit relationship between the T that _StateFullConsumerWidgetState is receiving as the type parameter and the T that StateFullConsumerWidget is using. Dart doesn't know how to reconcile this ambiguous relationship, so it defaults to the lowest common denominator the type constraints allow, which is ChangeNotifier.

Because of this, when you try to treat T as OnBoardingViewModel, Dart throws an error because, as far as the state class knows, the T of the parent widget is ChangeNotifier, not OnBoardingViewModel.

You can fix this by passing the type parameter along when you declare your state class:

class _StateFullConsumerWidgetState<T extends ChangeNotifier> 
    extends State<StateFullConsumerWidget<T>>