Kel
Kel

Reputation: 463

Flutter: How to show unique header in StickyHeader or SliverAppBar

I want to show unique header from JSON (Ex: "Header 1"; "Header 2"; "Header 3") like this:

enter image description here

this is JSON file:

[
  {
    "header": "Header 1",
    "line": "line 1 of Header 1"
  },
  {
    "header": "Header 1",
    "line": "line 2 of Header 1"
  },
  {
    "header": "Header 2",
    "line": "line 1 of Header 2"
  },
  {
    "header": "Header 2",
    "line": "line 2 of Header 2"
  },
  {
    "header": "Header 3",
    "line": "line 1 of Header 3"
  },
  {
    "header": "Header 3",
    "line": "line 2 of Header 3"
  }
]

so pls help me, I am using the package StickyHeader but I think that SliverAppBar can also do it, this is main file:

import 'package:ask/model/header_model.dart';
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:sticky_headers/sticky_headers/widget.dart';

class Page1 extends StatefulWidget {
  @override
  _Page1State createState() => _Page1State();
}

class _Page1State extends State<Page1> {
  Future<List<Header>> getHeaderFromJson(BuildContext context) async {
    String jsonString = await DefaultAssetBundle.of(context).loadString('assets/header.json');
    List<dynamic> raw = jsonDecode(jsonString);
    return raw.map((e) => Header.fromJson(e)).toList();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text('Header')),
        body: FutureBuilder(
            future: getHeaderFromJson(context),
            builder: (context, data) {
              List<Header> _header = data.data;
              return _header == null
                  ? Container()
                  : ListView.builder(
                      itemCount: _header.length,
                      itemBuilder: (context, index) {
                        return StickyHeader(
                          header: Container(
                            color: Colors.grey,
                            alignment: Alignment.center,
                            child: Text(_header[index].header), // I think this line needs to modify
                          ),
                          content: Column(
                            children: [Text(_header[index].line)],
                          ),
                        );
                      });
            }));
  }
}

Upvotes: 0

Views: 667

Answers (1)

Alok
Alok

Reputation: 8998

I know that you are confused. But let me tell you one thing, the way you are setting up the widget is wrong, as per your requirements.

If you closely take a look at the data, this is something like this:

[
  {
    "header": "Header 1",
    "line": "line 1 of Header 1"
  },
  {
    "header": "Header 1",
    "line": "line 2 of Header 1"
  },
  {
    "header": "Header 2",
    "line": "line 1 of Header 2"
  },
  {
    "header": "Header 2",
    "line": "line 2 of Header 2"
  },
  {
    "header": "Header 3",
    "line": "line 1 of Header 3"
  },
  {
    "header": "Header 3",
    "line": "line 2 of Header 3"
  }
]

Now if you're looping through the data via your ListView.builder, the machine will print out one item at a time, hence you are seeing the output on your Device screen like that.

// Just do that and see for yourself, what the outcome is there
// suppose we name your array data as _data
_data.forEach((element){
  print(element["header"]);
  print(element["list"]);
});

/* The output you will get it like this only
Header 1
line 1 of Header 1
Header 1
line 2 of Header 1
Header 2
line 1 of Header 2
Header 2
line 2 of Header 2
Header 3
line 1 of Header 3
Header 3
line 2 of Header 3
*/

And this is how you are getting on your screen, isn't it? The machine is doing the job in this way.

SOLUTION: What is required for you to do is to organize the item, such that your "header" should contain all the "line" associated to the right header only. You can do it via creating your Map and store the data like this:

  Map _newData = {};
  
  // basic idea is, if the header is same, the value has all the lines 
  // aligned to it
  _data.forEach((element){
    if(_newData.containsKey(element["header"])){
      _newData[element["header"]].add(element["line"]);
    }else{
      _newData[element["header"]] = [element["line"]];
    }
  });
  
  print(_newData);

  //OUTPUT will come like this
  // {Header 1: [line 1 of Header 1, line 2 of Header 1], Header 2: [line 1 of Header 2, line 2 of Header 2], Header 3: [line 1 of Header 3, line 2 of Header 3]}

Can you the data now, Header 1 has all the line associated to it, and so on.

So, now how we can achieve this in our app. I have not used any StickyHeader or something, with simple Flutter widgets, I have recreated your UI. See, and hope you can learn something new

class _MyHomePageState extends State<MyHomePage> {
  // Used the data directly, since I don't have the API data
  List<Map> _data = [
    {
      "header": "Header 1",
      "line": "line 1 of Header 1"
    },
    {
      "header": "Header 1",
      "line": "line 2 of Header 1"
    },
    {
      "header": "Header 2",
      "line": "line 1 of Header 2"
    },
    {
      "header": "Header 2",
      "line": "line 2 of Header 2"
    },
    {
      "header": "Header 3",
      "line": "line 1 of Header 3"
    },
    {
      "header": "Header 3",
      "line": "line 2 of Header 3"
    }
  ];
  
  // This will give out our final widget with header and data
  Widget get myWidget{
    // Consists of all the Widgets
    List<Widget> _widget = [];
    
    // our new data to contain {"header": [line]}
    Map _newData = {};
    
    // adding the same to it
    _data.forEach((element){
      if(_newData.containsKey(element["header"])){
        _newData[element["header"]].add(element["line"]);
      }else{
        _newData[element["header"]] = [element["line"]];
      }
    });
    
    // now finally traversing through our _newData to add the widget accordingly
    _newData.forEach((k,v){
      // This will add the subitems of the particular Header
      List<Widget> _getWidget = [];
      
      // Adding header items to the Container
      _widget.add(
        Container(
          width: double.infinity,
          color: Colors.grey,
          child: Text(k, textAlign: TextAlign.center)
      ));
       
     // traversing through line array for a particular array 
     // And adding to the Widget with the border underline
      v.forEach((data){
        _getWidget.add(
          Text(data)
        ); 
        _getWidget.add(
          Divider(height: 1.0, color: Colors.grey[300])
        );
      });
      
     // Finally adding the list of lines under the header
      _widget.add(
        Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: _getWidget
        )
      );
    });
    
    // returning the final widget in Column
    // Since our _widget is a list of widgets
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: _widget
    );
  }
  

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Container(
         height: double.infinity,
         width: double.infinity,
         child: this.myWidget
      )
    );
  }
}

FINAL OUTCOME

This is how, your app would look like:

New Image

Upvotes: 2

Related Questions