Flutter Android应用无法连接本地Node.js API问题求助
Flutter Android应用无法连接本地Node.js API问题
错误信息
I/flutter ( 4159): Error: SocketException: Connection refused (OS Error: Connection refused, errno = 111), address = localhost, port = 39394
Android编译提示
Note: Some input files use or override a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
Note: Some input files use unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
问题说明
- iOS端可正常连接本地Node.js API,Android端无法连接,已授予应用网络权限
- Android应用启动时出现上述编译警告,但可正常启动
相关代码
product.dart
import 'package:fiyatfind/constants/animation.dart'; import 'package:fiyatfind/constants/colors.dart'; import 'package:fiyatfind/constants/size.dart'; import 'package:fiyatfind/services/product_service.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; class ProductScreen extends StatefulWidget { final String barcode; const ProductScreen({super.key, required this.barcode}); @override State<ProductScreen> createState() => _ProductScreenState(); } class _ProductScreenState extends State<ProductScreen> { late ProductService productService; late Future<void> productFuture; @override void initState() { super.initState(); productService = Get.put( ProductService()); // initState içinde ProductService'yi başlatıyoruz productFuture = getProduct(); } Future<void> getProduct() async { await productService.getProduct(widget.barcode); if (kDebugMode) { print("çalıştı"); } } @override Widget build(BuildContext context) { final width = MediaQuery.of(context).size.width; return Scaffold( appBar: AppBar( automaticallyImplyLeading: false, elevation: 0, backgroundColor: Colors.transparent, title: Padding( padding: const EdgeInsets.only(left: 10), child: Row( children: [ CircleAvatar( radius: 25, backgroundColor: MyColor.fadeTuruncu, child: IconButton( splashColor: Colors.transparent, highlightColor: Colors.transparent, onPressed: () { Get.back(); }, icon: const Icon( Icons.arrow_back_ios_new, color: MyColor.turuncu, ), ), ), ], ), ), ), body: FutureBuilder( future: productFuture, builder: (context, snapshot) { if (snapshot.hasError) { return const Center(child: Text("Beklenmeyen bir hata oluştu")); } else if (snapshot.connectionState == ConnectionState.waiting) { return Center(child: AnimationProvider.animation()); } else { return productService.products.isEmpty ? Center( child: Text( "Ürün bulunamadı", style: TextStyle( fontSize: FontSize.setBigSize(width), color: MyColor.turuncu), )) : ListView.builder( itemCount: productService.products.length, itemBuilder: (context, index) { return Column( children: [ Padding( padding: EdgeInsets.symmetric( vertical: PaddingSize.setBigSize(width), horizontal: PaddingSize.setNormalSize(width)), child: productService.products[index].resim == null ? Image.asset( "assets/images/noimage.png", width: width * 0.5, ) : Image.network( productService.products[index].resim, width: width * 0.75)), Stack( children: [ Container( padding: EdgeInsets.only( bottom: PaddingSize.setNormalSize(width)), width: width * 0.8, decoration: BoxDecoration( color: MyColor.koyuGri, borderRadius: BorderRadius.circular(20)), child: Padding( padding: EdgeInsets.symmetric( horizontal: PaddingSize.setNormalSize(width)), child: Column( children: [ const SizedBox( height: 30, ), Text( productService .products[index].brandName, style: TextStyle( fontSize: FontSize.setBigSize(width), color: Colors.white, fontWeight: FontWeight.bold), ), Padding( padding: EdgeInsets.only( top: PaddingSize.setNormalSize( width)), child: Row( mainAxisAlignment: MainAxisAlignment .spaceBetween, children: [ Text( "Fiyat 1:", style: TextStyle( fontSize: FontSize .setNotNormalNotBig( width), color: MyColor.turuncu, fontWeight: FontWeight.bold), ), Text( productService.products[index] .satisFiyati == null ? "Fiyat Bilinmiyor" : "${productService.products[index].satisFiyati} TL", style: TextStyle( fontSize: FontSize .setNotNormalNotBig( width), color: Colors.white, fontWeight: FontWeight.bold), ), ], ), ), Padding( padding: EdgeInsets.only( top: PaddingSize.setSmallSize( width)), child: Row( mainAxisAlignment: MainAxisAlignment .spaceBetween, children: [ Text( "Fiyat 2:", style: TextStyle( fontSize: FontSize .setNotNormalNotBig( width), color: MyColor.turuncu, fontWeight: FontWeight.bold), ), Text( productService.products[index] .satisFiyati2 == null ? "Fiyat Bilinmiyor" : "${productService.products[index].satisFiyati2} TL", style: TextStyle( fontSize: FontSize .setNotNormalNotBig( width), color: Colors.white, fontWeight: FontWeight.bold), ), ], ), ), Padding( padding: EdgeInsets.only( top: PaddingSize.setSmallSize( width)), child: Row( mainAxisAlignment: MainAxisAlignment .spaceBetween, children: [ Text( "Fiyat 3:", style: TextStyle( fontSize: FontSize .setNotNormalNotBig( width), color: MyColor.turuncu, fontWeight: FontWeight.bold), ), Text( productService.products[index] .satisFiyati3 == null ? "Fiyat Bilinmiyor" : "${productService.products[index].satisFiyati3} TL", style: TextStyle( fontSize: FontSize .setNotNormalNotBig( width), color: Colors.white, fontWeight: FontWeight.bold), ), ], ), ), Padding( padding: EdgeInsets.only( top: PaddingSize.setSmallSize( width)), child: Row( mainAxisAlignment: MainAxisAlignment .spaceBetween, children: [ Text( "Taksitli Fiyat:", style: TextStyle( fontSize: FontSize .setNotNormalNotBig( width), color: MyColor.turuncu, fontWeight: FontWeight.bold), ), Text( productService.products[index] .taksitliFiyat == null ? "Fiyat Bilinmiyor" : "${productService.products[index].taksitliFiyat} TL", style: TextStyle( fontSize: FontSize .setNotNormalNotBig( width), color: Colors.white, fontWeight: FontWeight.bold), ), ], ), ), Padding( padding: EdgeInsets.only( top: PaddingSize.setSmallSize( width)), child: Row( mainAxisAlignment: MainAxisAlignment .spaceBetween, children: [ Text( "Stok Miktarı", style: TextStyle( fontSize: FontSize .setNotNormalNotBig( width), color: MyColor.turuncu, fontWeight: FontWeight.bold), ), Text( productService.products[index] .onHand == null ? "Stok Bilinmiyor" : "${productService.products[index].onHand} ${productService.products[index].unit}", style: TextStyle( fontSize: FontSize .setNotNormalNotBig( width), color: Colors.white, fontWeight: FontWeight.bold), ), ], ), ), Padding( padding: EdgeInsets.only( top: PaddingSize.setNormalSize( width)), child: SizedBox( width: width, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Tedarikçi Firma", textAlign: TextAlign.left, style: TextStyle( fontSize: FontSize .setNotNormalNotBig( width), color: MyColor.turuncu, fontWeight: FontWeight.bold)), Padding( padding: EdgeInsets.only( top: PaddingSize .setSmallSize( width)), child: Text( productService .products[index] .saticiFirma ?? "Tedarikçi Bilinmiyor", style: TextStyle( fontSize: FontSize .setBigSize( width), color: Colors.white, fontWeight: FontWeight.bold), ), ), ], ), ), ) ], ), ), ) ], ) ], ); }); } })); } }
product_service.dart
import 'package:dio/dio.dart'; import 'package:fiyatfind/models/product_model.dart'; import 'package:fiyatfind/providers/database_providers.dart'; import 'package:flutter/foundation.dart'; import 'package:get/get.dart'; class ProductService { Dio dio = Dio(); final accessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2ODkzNjAzOTd9._RDmnzGWFNE02R4i_3IlrHyNW_UX6bnJB_lTT3__QtY"; final url = "http://localhost:3000/product"; List<ProductModel> products = []; DatabaseConfig dbconfig = Get.find(); Future<List<ProductModel>> getProduct(String productCode) async { if (kDebugMode) { print(dbconfig.host.value); } if (kDebugMode) { print(dbconfig.username.value); } if (kDebugMode) { print(dbconfig.password.value); } if (kDebugMode) { print(dbconfig.databaseName.value); } if (kDebugMode) { print(productCode); } try { var response = await dio.post(url, data: { "host": dbconfig.host.value, "username": dbconfig.username.value, "password": dbconfig.password.value, "database": dbconfig.databaseName.value, "productCode": productCode }, options: Options( headers: {'Authorization': 'Bearer $accessToken'}, )); if (response.statusCode == 200) { products.clear(); products = (response.data as List) .map((e) => ProductModel.fromJson(e)) .toList(); return products; } else { return []; } } catch (e) { if (kDebugMode) { print(e); } return []; } } }
解决方案
核心问题:Android模拟器的localhost指向自身而非主机
Android模拟器内的localhost(127.0.0.1)指代模拟器系统本身,而非运行Node.js的主机电脑,因此会触发连接拒绝错误。
1. 修改API访问地址
- 使用主机局域网IP:在
product_service.dart中,将localhost替换为主机电脑的局域网IP(如192.168.1.100):final url = "http://192.168.1.100:3000/product"; - 使用模拟器特殊映射地址:Android模拟器提供
10.0.2.2作为主机localhost的映射地址,直接替换即可:final url = "http://10.0.2.2:3000/product";
2. 确保Node.js API允许外部访问
启动Node.js服务时,监听地址设置为0.0.0.0(而非默认的localhost),让局域网内设备(包括模拟器)能正常访问:
app.listen(3000, '0.0.0.0', () => { console.log('Server running on port 3000'); });
3. 检查防火墙设置
确认主机电脑的防火墙允许3000端口的入站连接,避免拦截模拟器的请求。
关于编译警告
这些警告是依赖库使用了已废弃API或未检查的操作,不影响应用核心功能。若要消除警告:
- 在Android项目的
build.gradle中添加lint参数:android { lintOptions { deprecation true unchecked true } } - 或更新相关依赖库到最新版本,避免使用废弃API。
内容的提问来源于stack exchange,提问作者MertYavuz
相关产品推荐
相关产品推荐

