Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter app not displaying data from Node.js API (Express + HTTP request)

Problem: I am developing a Flutter app that fetches coffee data from a Node.js Express API using an HTTP request. The API works fine when tested in Postman and returns the expected JSON response. However, when I try to fetch and display the data in my Flutter app, no data appears in the UI.

The console shows that the response is being received correctly, but the ListView remains empty. There are no errors, and the API response prints correctly in the console.

Here’s my Node.js API code:

const express = require('express');
const cors = require('cors');
const { v4: uuidv4 } = require('uuid');

const app = express();
const PORT = process.env.PORT || 3000;

// Middleware
app.use(cors());
app.use(express.json());
app.use('/assets', express.static('assets')); // Serve static files

// Sample Coffee Data
let coffees = [
    { id: "11111111-1111-1111-1111-111111111111", image: "assets/images.jpeg", name: "Caffe Mocha", type: "Deep Foam", price: 5.5, rating: 4.8 },
    { id: "22222222-2222-2222-2222-222222222222", image: "assets/download.jpeg", name: "Flat White", type: "Espresso", price: 3.53, rating: 4.8 }
];

// ✅ Get all coffee items
app.get('/api/coffees', (req, res) => {
    res.json(coffees);
});

// ✅ Get a single coffee by ID
app.get('/api/coffees/:id', (req, res) => {
    const coffee = coffees.find(c => c.id === req.params.id);
    if (!coffee) {
        return res.status(404).json({ error: "Coffee not found" });
    }
    res.json(coffee);
});

// ✅ Create a new coffee item
app.post('/api/coffees', (req, res) => {
    const { image, name, type, price, rating } = req.body;

    // Validate required fields
    if (!image || !name || !type || price == null || rating == null) {
        return res.status(400).json({ error: "All fields are required" });
    }

    // Ensure price and rating are numbers
    if (isNaN(price) || isNaN(rating)) {
        return res.status(400).json({ error: "Price and rating must be numbers" });
    }

    // Create new coffee item
    const newCoffee = { id: uuidv4(), image, name, type, price, rating };
    coffees.push(newCoffee);

    res.status(201).json(newCoffee);
});

// ✅ Handle invalid JSON errors
app.use((err, req, res, next) => {
    if (err instanceof SyntaxError && err.status === 400 && 'body' in err) {
        return res.status(400).json({ error: "Invalid JSON format" });
    }
    next();
});

// ✅ Start the Server
app.listen(PORT, () => {
    console.log(`✅ Server running on http://localhost:${PORT}`);
});

this is my flutter code


class CoffeeShopScreen extends StatefulWidget {
  @override
  _CoffeeShopScreenState createState() => _CoffeeShopScreenState();
}

class _CoffeeShopScreenState extends State<CoffeeShopScreen> {
  List<dynamic> coffeeList = [];
  bool isLoading = true;
  bool isError = false;

  @override
  void initState() {
    super.initState();
    fetchData(); // ✅ Call fetchData when the screen loads
  }
  Future<void> fetchData() async {
    setState(() {
      isLoading = true;
      isError = false;
    });

    try {
      final response = await http.get(Uri.parse('http://10.0.2.2:3000/api/coffees'));
      print("Response Status: ${response.statusCode}");
      print("Response Body: ${response.body}");

      if (response.statusCode == 200) {
        final List<dynamic> jsonData = jsonDecode(response.body);
        setState(() {
          coffeeList = jsonData;
          isLoading = false;
        });
      } else {
        throw Exception("Failed to load data. Status Code: ${response.statusCode}");
      }
    } catch (e) {
      setState(() {
        isError = true;
        isLoading = false;
      });
      print("Error fetching data: $e");
    }
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      body: SingleChildScrollView(
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const SizedBox(height: 40),

              // Location
              Text("Location", style: GoogleFonts.poppins(color: Colors.grey, fontSize: 14)),
              const SizedBox(height: 4),
              Row(
                children: [
                  Text(
                    "Bilzen, Tanjungbalai",
                    style: GoogleFonts.poppins(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold),
                  ),
                  const Spacer(),
                  const Icon(Icons.more_vert, color: Colors.white),
                ],
              ),
              const SizedBox(height: 16),

              // Search bar
              Container(
                decoration: BoxDecoration(color: Colors.grey[900], borderRadius: BorderRadius.circular(12)),
                padding: const EdgeInsets.symmetric(horizontal: 16),
                child: TextField(
                  style: TextStyle(color: Colors.white),
                  decoration: InputDecoration(
                    icon: Icon(Icons.search, color: Colors.white),
                    hintText: "Search coffee",
                    hintStyle: TextStyle(color: Colors.grey),
                    border: InputBorder.none,
                  ),
                ),
              ),

              const SizedBox(height: 16),

              // Promo Banner
              Container(
                decoration: BoxDecoration(color: Colors.brown[700], borderRadius: BorderRadius.circular(16)),
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Container(
                      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
                      decoration: BoxDecoration(color: Colors.red, borderRadius: BorderRadius.circular(12)),
                      child: Text("Promo", style: GoogleFonts.poppins(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold)),
                    ),
                    const SizedBox(height: 8),
                    Text(
                      "Buy one get\none FREE",
                      style: GoogleFonts.poppins(color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
                    ),
                  ],
                ),
              ),

              const SizedBox(height: 16),

              // Coffee Categories
              SingleChildScrollView(
                scrollDirection: Axis.horizontal,
                child: Row(
                  children: [
                    CoffeeCategory(title: "All Coffee", isSelected: true),
                    CoffeeCategory(title: "Macchiato"),
                    CoffeeCategory(title: "Latte"),
                    CoffeeCategory(title: "Americano"),
                  ],
                ),
              ),

              const SizedBox(height: 16),

              // Coffee Items from API
              if (isLoading)
                Center(child: CircularProgressIndicator())
              else if (isError)
                Center(child: Text("Error loading data", style: TextStyle(color: Colors.red)))
              else
                Column(
                  children: [
                    for (int i = 0; i < coffeeList.length; i += 2)
                      Row(
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: [
                          Expanded(
                            child: CoffeeCard(
                              image: coffeeList[i]['image'],
                              name: coffeeList[i]['name'],
                              type: coffeeList[i]['type'],
                              price: "\$${coffeeList[i]['price']}",
                              rating: coffeeList[i]['rating'].toString(),
                            ),
                          ),
                          const SizedBox(width: 16),
                          if (i + 1 < coffeeList.length)
                            Expanded(
                              child: CoffeeCard(
                                image: coffeeList[i + 1]['image'],
                                name: coffeeList[i + 1]['name'],
                                type: coffeeList[i + 1]['type'],
                                price: "\$${coffeeList[i + 1]['price']}",
                                rating: coffeeList[i + 1]['rating'].toString(),
                              ),
                            ),
                        ],
                      ),
                  ],
                ),
            ],
          ),
        ),
      ),
    );
  }
}

// Coffee Category Widget
class CoffeeCategory extends StatelessWidget {
  final String title;
  final bool isSelected;
  const CoffeeCategory({required this.title, this.isSelected = false});

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(right: 8.0),
      child: Container(
        padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
        decoration: BoxDecoration(color: isSelected ? Colors.orange : Colors.grey[800], borderRadius: BorderRadius.circular(12)),
        child: Text(
          title,
          style: GoogleFonts.poppins(color: Colors.white, fontSize: 14, fontWeight: FontWeight.bold),
        ),
      ),
    );
  }
}

  // Coffee Card Widget
  class CoffeeCard extends StatelessWidget {
    final String image;
    final String name;
    final String type;
    final String price;
    final String rating;

    const CoffeeCard({required this.image, required this.name, required this.type, required this.price, required this.rating});

    @override
    Widget build(BuildContext context) {
      return Container(
        decoration: BoxDecoration(color: Colors.grey[900], borderRadius: BorderRadius.circular(16)),
        padding: const EdgeInsets.all(12),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Image.network(image, height: 100, width: double.infinity, fit: BoxFit.cover,
                errorBuilder: (context, error, stackTrace) => Icon(Icons.image, size: 100, color: Colors.white)),
            const SizedBox(height: 8),
            Text(name, style: GoogleFonts.poppins(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold)),
            Text(type, style: GoogleFonts.poppins(color: Colors.grey, fontSize: 14)),
            const SizedBox(height: 8),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text(price, style: GoogleFonts.poppins(color: Colors.orange, fontSize: 16, fontWeight: FontWeight.bold)),
                Row(children: [Icon(Icons.star, color: Colors.orange, size: 16), Text(rating, style: GoogleFonts.poppins(color: Colors.white, fontSize: 14))]),
              ],
            ),
          ],
        ),
      );
    }
  }


What I’ve Tried:

The API works fine in Postman (http://localhost:3000/api/coffees). The Flutter app prints the response status and body correctly. However, no data appears in the UI.

like image 426
Prashant Shukla Avatar asked Oct 15 '25 16:10

Prashant Shukla


1 Answers

ListView.builder(
  shrinkWrap: true,
  physics: NeverScrollableScrollPhysics(),  
  itemCount: (coffeeList.length / 2).ceil(),
  itemBuilder: (context, index) {
    int i = index * 2;
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Expanded(
          child: CoffeeCard(
            image: coffeeList[i]['image'],
            name: coffeeList[i]['name'],
            type: coffeeList[i]['type'],
            price: "\$${coffeeList[i]['price']}",
            rating: coffeeList[i]['rating'].toString(),
          ),
        ),
        const SizedBox(width: 16),
        if (i + 1 < coffeeList.length)
          Expanded(
            child: CoffeeCard(
              image: coffeeList[i + 1]['image'],
              name: coffeeList[i + 1]['name'],
              type: coffeeList[i + 1]['type'],
              price: "\$${coffeeList[i + 1]['price']}",
              rating: coffeeList[i + 1]['rating'].toString(),
            ),
          ),
      ],
    );
  },
)

Try above code. if not resolve the issue plz add response

like image 63
Punam Panchbhai Avatar answered Oct 18 '25 10:10

Punam Panchbhai