flutter_decision_making 1.0.6 copy "flutter_decision_making: ^1.0.6" to clipboard
flutter_decision_making: ^1.0.6 copied to clipboard

Dart package for multi-criteria decision making. Currently supports Analytic Hierarchy Process (AHP) with plans to add other popular algorithms like SAW and TOPSIS.

example/lib/main.dart

import 'package:example/helper.dart';
import 'package:example/show_pairwise_comparison_scale_dialog.dart';
import 'package:flutter/material.dart';
import 'package:flutter_decision_making/flutter_decision_making.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
      ),

      home: const AHPPage(title: 'Flutter Decision Making Example'),
    );
  }
}

class AHPPage extends StatefulWidget {
  const AHPPage({super.key, required this.title});

  final String title;

  @override
  State<AHPPage> createState() => _AHPPageState();
}

class _AHPPageState extends State<AHPPage> {
  final _criteriaController = TextEditingController();
  final _alternativeController = TextEditingController();
  late TextStyle _textStyle;
  late AHP _ahp;
  late List<AhpHierarchy> _listHierarchy;
  late List<AhpItem> _listCriteria;
  late List<AhpItem> _listAlternative;
  late List<PairwiseComparisonInput> _inputCriteria;
  late List<PairwiseAlternativeInput> _inputAlternative;
  late Helper _helper;
  AhpResult? _ahpResult;

  @override
  void initState() {
    super.initState();
    _ahp = AHP();
    _textStyle = TextStyle(fontSize: 18);
    _listHierarchy = [];
    _listAlternative = [];
    _listCriteria = [];
    _inputCriteria = [];
    _inputAlternative = [];
    _helper = Helper();
  }

  @override
  void dispose() {
    super.dispose();
    _criteriaController.dispose();
    _alternativeController.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final paddingBottom = MediaQuery.of(context).viewInsets.bottom;
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: SafeArea(
        child: Padding(
          padding: EdgeInsets.symmetric(vertical: 10, horizontal: 10),
          child: ListView(
            children: [
              /// CRITERIA YOU WANT
              _buildInputWidget(
                title: 'Criteria',
                controller: _criteriaController,
                onPressed:
                    () => _addItem(
                      _criteriaController,
                      _listCriteria,
                      (name) => AhpItem(name: name),
                    ),
              ),

              /// LIST CRITERIA
              _listCriteria.isNotEmpty
                  ? Container(
                    constraints: BoxConstraints(maxHeight: 100),
                    margin: EdgeInsets.only(bottom: 10, top: 10),
                    padding: EdgeInsets.symmetric(vertical: 5, horizontal: 5),
                    decoration: BoxDecoration(
                      border: Border.all(color: Colors.black),
                    ),
                    width: double.infinity,
                    child: Scrollbar(
                      child: ListView.builder(
                        itemCount: _listCriteria.length,
                        itemBuilder: (context, index) {
                          var data = _listCriteria[index];
                          return Text(
                            '${index + 1}. ${data.name}',
                            style: _textStyle,
                          );
                        },
                      ),
                    ),
                  )
                  : SizedBox(height: 10),

              const Divider(),

              /// ALTERNATIVE YOU HAVE
              _buildInputWidget(
                title: 'Alternative',
                controller: _alternativeController,
                onPressed:
                    () => _addItem(
                      _alternativeController,
                      _listAlternative,
                      (name) => AhpItem(name: name),
                    ),
              ),

              /// LIST ALTERNATIVE
              _listAlternative.isNotEmpty
                  ? Container(
                    constraints: BoxConstraints(maxHeight: 100),
                    margin: EdgeInsets.only(bottom: 20, top: 10),
                    padding: EdgeInsets.symmetric(vertical: 5, horizontal: 5),
                    decoration: BoxDecoration(
                      border: Border.all(color: Colors.black),
                    ),
                    width: double.infinity,
                    child: Scrollbar(
                      child: ListView.builder(
                        itemCount: _listAlternative.length,
                        itemBuilder: (context, index) {
                          var data = _listAlternative[index];
                          return Text(
                            '${index + 1}. ${data.name}',
                            style: _textStyle,
                          );
                        },
                      ),
                    ),
                  )
                  : SizedBox(height: 20),

              /// GENERATE HIERARCHY STRUCTURE & PAIRWISE MATRIX TEMPLATE
              Center(
                child: ElevatedButton(
                  onPressed: () async {
                    /// GENERATE HIERARCHY FIRST BEFORE GENERATE CRITERIA INPUTS OR ALTERNATIVE INPUTS
                    _listHierarchy = await _ahp
                        .generateHierarchy(
                          listCriteria: _listCriteria,
                          listAlternative: _listAlternative,
                        )
                        .catchError((e) {
                          if (context.mounted) {
                            _helper.showScaffoldMessenger(
                              context: context,
                              message: e.toString(),
                            );
                          }

                          return <AhpHierarchy>[];
                        })
                        .whenComplete(() {});

                    /// GENERATE CRITERIA INPUTS
                    _inputCriteria = await _ahp.generateCriteriaInputs();

                    /// GENERATE ALTERNATIVE INPUTS
                    _inputAlternative = await _ahp.generateAlternativeInputs(
                      hierarchyNodes: _listHierarchy,
                    );

                    Future.delayed(Duration(milliseconds: 300), () {
                      setState(() {});
                    });
                  },
                  child: Text(
                    'Generate Hierarchy Structure and Pairwise Matrix Template',
                    style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                    textAlign: TextAlign.center,
                  ),
                ),
              ),

              /// PAIRWISE CRITERIA INPUTS
              _inputCriteria.isNotEmpty
                  ? Container(
                    margin: EdgeInsets.only(top: 20),
                    padding: EdgeInsets.all(8),
                    decoration: BoxDecoration(
                      color: Colors.grey.shade100,
                      border: Border.all(color: Colors.black),
                    ),
                    child: Column(
                      mainAxisSize: MainAxisSize.min,
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          'Pairwise Criteria',
                          style: TextStyle(
                            fontWeight: FontWeight.bold,
                            fontSize: 20,
                          ),
                        ),

                        /// CRITERIA ITEMS
                        Container(
                          color: Colors.grey.shade300,
                          height: 200,
                          child: Scrollbar(
                            child: ListView.builder(
                              itemCount: _inputCriteria.length,
                              padding: EdgeInsets.only(right: 10),
                              itemBuilder: (context, index) {
                                var criteria = _inputCriteria[index];
                                return Row(
                                  children: [
                                    /// NAME
                                    Text(criteria.left.name, style: _textStyle),

                                    /// SELECT VALUE COMPARISON
                                    Expanded(
                                      child: InkWell(
                                        onTap: () {
                                          /// SHOW COMPARISON SCALE DIALOG
                                          showPairwiseComparisonScaleDialog(
                                            context,
                                            comparison:
                                                _ahp.listPairwiseComparisonScale,
                                            leftItemName: criteria.left.name,
                                            rightItemName: criteria.right.name,
                                            onSelected: (scale, important) {
                                              if (scale != null &&
                                                  important != null) {
                                                /// UPDATE CRITERIA VALUE
                                                _inputCriteria = _ahp
                                                    .updateCriteriaInputs(
                                                      _inputCriteria,
                                                      id: criteria.id,
                                                      scale: scale.value,
                                                      isLeftMoreImportant:
                                                          important,
                                                    );

                                                setState(() {});
                                              }
                                            },
                                          );
                                        },
                                        child: Container(
                                          height: 40,
                                          width: double.infinity,
                                          alignment: Alignment.centerLeft,
                                          padding: EdgeInsets.symmetric(
                                            horizontal: 5,
                                          ),
                                          margin: EdgeInsets.symmetric(
                                            horizontal: 10,
                                            vertical: 5,
                                          ),
                                          decoration: BoxDecoration(
                                            border: Border.all(
                                              color: Colors.black,
                                            ),
                                          ),
                                          child: Text(
                                            criteria.preferenceValue != null
                                                ? '${criteria.preferenceValue} - ${criteria.isLeftMoreImportant == true ? 'left item is more important' : 'right item is more important'}'
                                                : 'please select scale comparison',
                                            style: _textStyle,
                                          ),
                                        ),
                                      ),
                                    ),

                                    /// NAME
                                    Text(
                                      criteria.right.name,
                                      style: _textStyle,
                                    ),
                                  ],
                                );
                              },
                            ),
                          ),
                        ),
                      ],
                    ),
                  )
                  : const SizedBox(),

              /// PAIRWISE ALTERNATIVE INPUTS
              _inputAlternative.isNotEmpty
                  ? Container(
                    margin: EdgeInsets.only(top: 20),
                    padding: EdgeInsets.all(8),
                    decoration: BoxDecoration(
                      color: Colors.grey.shade100,
                      border: Border.all(color: Colors.black),
                    ),
                    child: Column(
                      mainAxisSize: MainAxisSize.min,
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        /// TITLE
                        Text(
                          'Pairwise Alternative',
                          style: TextStyle(
                            fontWeight: FontWeight.bold,
                            fontSize: 20,
                          ),
                        ),

                        /// ALTERNATIVE ITEMS
                        Container(
                          height: 200,
                          color: Colors.grey.shade300,
                          child: Scrollbar(
                            child: ListView.builder(
                              itemCount: _inputAlternative.length,
                              padding: EdgeInsets.only(right: 10),
                              itemBuilder: (context, index) {
                                final data = _inputAlternative[index];
                                return Column(
                                  mainAxisSize: MainAxisSize.min,
                                  crossAxisAlignment: CrossAxisAlignment.start,
                                  children: [
                                    Text(
                                      data.criteria.name,
                                      style: _textStyle.copyWith(
                                        fontWeight: FontWeight.bold,
                                      ),
                                    ),

                                    ...data.alternative.map(
                                      (e) => Row(
                                        children: [
                                          /// NAME
                                          Text(e.left.name, style: _textStyle),

                                          /// SELECT VALUE COMPARISON
                                          Expanded(
                                            child: InkWell(
                                              onTap: () {
                                                /// SHOW COMPARISON SCALE DIALOG
                                                showPairwiseComparisonScaleDialog(
                                                  context,
                                                  comparison:
                                                      _ahp.listPairwiseComparisonScale,
                                                  leftItemName: e.left.name,
                                                  rightItemName: e.right.name,
                                                  onSelected: (
                                                    scale,
                                                    important,
                                                  ) {
                                                    if (scale != null &&
                                                        important != null) {
                                                      /// UPDATE ALTERNATIVE VALUE
                                                      _inputAlternative = _ahp
                                                          .updateAlternativeInputs(
                                                            _inputAlternative,
                                                            criteriaId:
                                                                data
                                                                    .criteria
                                                                    .id,
                                                            alternativeId: e.id,
                                                            scale: scale.value,
                                                            isLeftMoreImportant:
                                                                important,
                                                          );

                                                      setState(() {});
                                                    }
                                                  },
                                                );
                                              },
                                              child: Container(
                                                height: 40,
                                                width: double.infinity,
                                                alignment: Alignment.centerLeft,
                                                padding: EdgeInsets.symmetric(
                                                  horizontal: 5,
                                                ),
                                                margin: EdgeInsets.symmetric(
                                                  horizontal: 10,
                                                  vertical: 5,
                                                ),
                                                decoration: BoxDecoration(
                                                  border: Border.all(
                                                    color: Colors.black,
                                                  ),
                                                ),
                                                child: Text(
                                                  e.preferenceValue != null
                                                      ? '${e.preferenceValue} - ${e.isLeftMoreImportant == true ? 'left item is more important' : 'right item is more important'}'
                                                      : 'please select scale comparison',
                                                  style: _textStyle,
                                                ),
                                              ),
                                            ),
                                          ),

                                          /// NAME
                                          Text(e.right.name, style: _textStyle),
                                        ],
                                      ),
                                    ),
                                  ],
                                );
                              },
                            ),
                          ),
                        ),
                      ],
                    ),
                  )
                  : const SizedBox(),

              /// GENERATE PAIRWISE MATRIX
              _inputAlternative.isNotEmpty && _inputCriteria.isNotEmpty
                  ? Padding(
                    padding: EdgeInsets.only(top: 20),
                    child: Center(
                      child: ElevatedButton(
                        onPressed: () async {
                          /// GET RESULT AHP
                          _ahpResult = await _ahp
                              .getAhpResult(
                                hierarchy: _listHierarchy,
                                inputsCriteria: _inputCriteria,
                                inputsAlternative: _inputAlternative,
                              )
                              .catchError((e) {
                                if (context.mounted) {
                                  /// SHOW MESSAGE IF CATCH EXCEPTION
                                  _helper.showScaffoldMessenger(
                                    context: context,
                                    message: e.toString(),
                                  );
                                }

                                return AhpResult();
                              });

                          Future.delayed(Duration(milliseconds: 300), () {
                            setState(() {});
                          });
                        },
                        child: Text(
                          'Generate Result',
                          style: TextStyle(
                            fontSize: 18,
                            fontWeight: FontWeight.bold,
                          ),
                          textAlign: TextAlign.center,
                        ),
                      ),
                    ),
                  )
                  : const SizedBox(),

              /// RESULT
              _ahpResult != null
                  ? Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Padding(
                        padding: EdgeInsets.symmetric(vertical: 10),
                        child: Divider(),
                      ),

                      Text(
                        'Result',
                        style: TextStyle(
                          fontWeight: FontWeight.bold,
                          fontSize: 20,
                        ),
                      ),

                      /// RESULT ITEMS
                      ListView.builder(
                        shrinkWrap: true,
                        physics: const NeverScrollableScrollPhysics(),
                        padding: EdgeInsets.only(top: 10),
                        itemCount: _ahpResult?.results?.length ?? 0,
                        itemBuilder: (context, index) {
                          var data = _ahpResult?.results?[index];
                          return Text(
                            '${data?.name}: ${data?.value}',
                            style: _textStyle,
                          );
                        },
                      ),

                      const Divider(),

                      /// RESULT CONSISTENCY CRITERIA RATIO
                      Text(
                        'criteria consistency ratio: ${_ahpResult?.consistencyCriteriaRatio}',
                        style: _textStyle,
                      ),

                      const Divider(),

                      /// IS VALID CRITERIA?
                      Text(
                        'is criteria consistent: ${_ahpResult?.isConsistentCriteria}',
                        style: _textStyle,
                      ),

                      const Divider(),

                      /// RESULT ALTERNATIVE RATIO
                      Text(
                        'alternative consistency ratio: ${_ahpResult?.consistencyAlternativeRatio}',
                        style: _textStyle,
                      ),

                      const Divider(),

                      /// IS VALID ALTERNATIVE?
                      Text(
                        'is alternative consistent: ${_ahpResult?.isConsistentAlternative}',
                        style: _textStyle,
                      ),

                      const Divider(),

                      /// NOTE WILL BE DISPLAYED IF THE CRITERIA OR ALTERNATIVES ARE INVALID
                      _ahpResult?.note != null
                          ? Text(_ahpResult!.note!, style: _textStyle)
                          : const SizedBox(),
                    ],
                  )
                  : const SizedBox(),

              SizedBox(height: paddingBottom),
            ],
          ),
        ),
      ),
    );
  }

  /// INPUT FOR CRITERIA OR ALTERNATIVE
  Widget _buildInputWidget({
    required String title,
    required TextEditingController controller,
    required Function() onPressed,
  }) {
    return Row(
      children: [
        Text(title, style: _textStyle),

        Expanded(
          child: Padding(
            padding: EdgeInsets.symmetric(horizontal: 10),
            child: TextFormField(
              controller: controller,
              decoration: InputDecoration(border: OutlineInputBorder()),
            ),
          ),
        ),

        ElevatedButton(onPressed: onPressed, child: Text('Add')),
      ],
    );
  }

  /// ADD ITEM TO CRITERIA OR ALTERNATIVE
  void _addItem<T>(
    TextEditingController controller,
    List<T> items,
    T Function(String name) createItem,
  ) {
    final value = controller.text.trim();
    if (value.isNotEmpty) {
      setState(() {
        items.add(createItem(value));
        controller.clear();
      });
    }
  }
}
1
likes
0
points
198
downloads

Publisher

unverified uploader

Weekly Downloads

Dart package for multi-criteria decision making. Currently supports Analytic Hierarchy Process (AHP) with plans to add other popular algorithms like SAW and TOPSIS.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter

More

Packages that depend on flutter_decision_making