I am developing a flutter application with network activities. To get data, I am connecting to a REST API, this API is fast as it should.
For more information, this API is using AWS API Gateway and AWS Lambda along with other AWS technologies.
Below is my code, connecting to network.
class RoleService with ChangeNotifier {
NavLinks _navLinks = NavLinks();
late List<Role> _roles;
/// Get all Roles
Future<void> getAllRoles(String authToken) async {
try {
var data = await http.get(
Uri.parse("https://api2.example.com/userrel/roles/getall"),
headers: {HttpHeaders.authorizationHeader: "Bearer $authToken"},
);
var jsonData =
convert.json.decode(data.body).cast<Map<String, dynamic>>();
_roles = jsonData.map<Role>((json) => new Role.fromJson(json)).toList();
print(_roles);
} catch (error) {
print(error);
throw error;
}
}
}
You can see the postman performance of the above API call below. For flutter testing, i am using Huawei p30 Lite android phone.
Then, when I execute the same API call in flutter, this is what I get.
Observing the outputs from postman I can see it has cached the DNS Lookup, TCP Handshake and SSL Handshake. postman does this after calling the API base URI for the first time. Then from the 2nd time onwards, the DNS Lookup etc are cached saving lot of time in future API calls to the same base URI.
But in flutter the "Connection established" time is high, even though the time to retrieve data is only few milliseconds.
How can I avoid the connection delays and get the maximum performance? If caching the SSL, DNS Lookup etc is the solution, how can I do that in flutter?
It seems this question is there for many people. So, let me answer my own question.
Can flutter remember the network connection? Yes it can.
Flutter only require one network call to the same API to remember the connection. From the second call onward to the same API, it will use its "cached" memory giving you a big performance boost.
So first remember, this only works if you are calling the same API multiple times. If you are calling different APIs, this will not work. However in many apps, you have an API that built by the API team and you will be calling the same throughput the app.
The solution is to use flutter http.Client. Then share the same http.Client across the calls you make to the same API. You will see only first call takes time for "connection", rest of the calls do not take that time.
An example is available in flutter http pub page. It says ,
If you're making multiple requests to the same server, you can keep
open a persistent connection by using a Client rather than making
one-off requests. If you do this, make sure to close the client when
you're done:
Check below example. It is only for your reference, not the best way of using this.
main.dart
import 'package:flutter/material.dart';
import 'package:network_test/role_service.dart';
import 'package:network_test/user_role_service.dart';
import 'package:network_test/user_service.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var startTime = "";
var endTime = "";
void _network() async {
var client = http.Client();
RoleService _roleService = RoleService();
UserService _userService = UserService();
UserRoleService _userRoleService = UserRoleService();
String authToken = "****";
String uid = "555555";
try {
await _roleService.getAllRoles(authToken, client);
//await _roleService.getAllRoles(authToken, client);
await _userService.getUserByUID(authToken, uid, client);
await _userService.getUserByID(authToken, 27, client);
await _userRoleService.getUserRoleByUser(authToken, 27, client);
} finally {
client.close();
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
"Start Time: " + startTime,
style: Theme.of(context).textTheme.headline4,
),
Text(
"End Time: " + endTime,
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _network,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
role_service.dart
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:http/http.dart';
import 'package:network_test/role.dart';
import 'dart:convert' as convert;
import 'dart:io';
class RoleService with ChangeNotifier {
late List<Role> _roles;
String link2 = "https://api2.somewhere.com/userrel";
/// Return roles
List<Role> returnRoles() {
return _roles;
}
/// Get all Roles
Future<void> getAllRoles(String authToken, Client client) async {
try {
var data = await client.get(Uri.parse(link2 + "/role/getall"),
headers: {HttpHeaders.authorizationHeader: "Bearer $authToken"});
var jsonData =
convert.json.decode(data.body).cast<Map<String, dynamic>>();
_roles = jsonData.map<Role>((json) => Role.fromJson(json)).toList();
print(_roles[0].roleName);
} catch (error) {
print(error);
throw error;
}
}
}
now I told you that above is not the best practice. Why? Because you will be creating and destroying the http.Client in many different places. Let's pay attention to a better practice.
In almost every app, we use State Management. I am a fan of Provider, it could be anything of your choice. i figured out the best way is to let the state management to remember the creation of http.Client. Since I am using Provider, I created the following class.
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
class ConnectionService with ChangeNotifier {
http.Client _client = http.Client();
http.Client returnConnection() {
return _client;
}
}
And this is my main class
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => ConnectionService()),
],
child: MyApp(),
));
}
Now when the app opens, I call the ConnectionService class to make the connection and do my API calls such as checking user authentication, user access etc. And only the first call is taking its time to build the connection, other calls do not.
Related
I am using flutter to open an http server on Android
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:jaguar/serve/server.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Http Server',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late Jaguar _server;
#override
void initState() {
super.initState();
init();
}
#override
void dispose() {
super.dispose();
_server.close();
}
void init() async {
try {
_server = Jaguar(port: 7777);
_server.get('/', (ctx) => 'Hello world!');
await _server.serve();
} catch (e) {
print("Something went wrong while creating a server...");
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
var url = Uri.parse("http://localhost:7777");
var response = await http.get(url);
print('Response status: ${response.statusCode}');
print('Response body: ${response.body}');
},
child: const Icon(Icons.add),
),
);
}
}
PC and Android real machine connected to the same WIFI
Test the app on the real machine and click add to get the expected results, but I cannot access this http service on the PC
$ curl http://192.168.9.102:7777
curl: (7) Failed to connect to 192.168.9.102 port 7777 after 2048 ms: Connection refused
I'm not sure what's causing it, my guess is that Android blocks all port connections in order to protect the phone's security, if so how do I expose port 7777 to the LAN
This is a routing problem.
Most WiFi routers have two relevant settings (its manual should suffice as reputable source):
When "connected to the same Wifi": enable the setting, which permits devices to communicate with each other. Else the devices cannot see each other.
For wired traffic: enable to bridge device on the WiFi router. Unless these two subnets are being bridged, the wired LAN traffic cannot be routed to the wireless network segment.
I found the problem, although the PC and android are connected to the same WIFI, but they are different ip
When I check the WIFI connection details of Android, I found that the ip is 192.168.9.101, which is completely different from the PC
$ curl http://192.168.9.101:7777
Hello world!
I am curious to use another Android phone to connect to the same WIFI and found that the ip is 192.168.9.100
So I am creating an application using Flutter, nodeJs and mongoDB.
So what I want to do in it is that when I post json data from nodejs backend to mongoDB, after being posted it should show up on my flutter app page. How can I do that (I have tried using websocket for my Flutter app, such that it constantly and actively listen to POST request(from nodejs to mongodb) from backend and try to take data from that request to display it. But websocket isn't working properly I guess, because the data isn't showing up on flutter app page.)
Here is the function (nodeJs) for posting the data to mongoDB database, (actions.js)
//Event is here a college event like webinar or something.
var Event = require('../models/user') //has the schema
var functions={
//for adding the new event
addNewEvent: function(req, res){
//if fields not filled
if((!req.body.Title ) || (!req.body.EventInfo)|| (!req.body.Date)|| (!req.body.RegDeadline)|| (!req.body.Timings)){
res.json({success:false, msg: 'Enter all fields'})
console.log("\n"+req.body.Title+"\n"+req.body.EventInfo+"\n"+req.body.Date+"\n"+req.body.RegDeadline+"\n"+req.body.Timings);
}
else{
var newEvent= Event({
Title: req.body.Title,
EventInfo: req.body.EventInfo,
Date: req.body.Date,
RegDeadline: req.body.RegDeadline,
Timings: req.body.Timings,
});
newEvent.save(function(err, newEvent){
if(err){
res.json({success:false, msg: 'Failed to save event'})
}
else{
res.json({success:true, msg: 'Successfully saved new event'})
}
})
}
module.exports=functions
I run this on localhost (port:8080).
Here is the file containing routes:
const express = require('express')
const actions = require('../methods/actions')
const router = express.Router()
//we send a message to our server (makes a GET request)
router.get('/',(req, res)=>{
res.send('Your server is running...')
})
//routing to another page section on our port
router.get('/dashboard',(req, res)=>{
res.send('Dashboard')
})
]
router.post('/addevent',actions.addNewEvent)
router.get('/addevent',actions.addNewEvent)
module.exports = router
Here is the dart file that uses websocket and tries to listen if any recent post request has been made, if it has been made take json data from it and display it.
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(
channel: WebSocketChannel.connect(
Uri.parse("ws://localhost:8080/addevent"))
//channel: IOWebSocketChannel.connect("ws://localhost:8080/addevent"),
),
);
}
}
class MyHomePage extends StatefulWidget {
final WebSocketChannel channel;
MyHomePage({required this.channel});
#override
MyHomePageState createState() {
return new MyHomePageState();
}
}
class MyHomePageState extends State<MyHomePage> {
//TextEditingController editingController = new TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Web Socket"),
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
StreamBuilder(
stream: widget.channel.stream,
builder: (context, snapshot) {
return Padding(
padding: const EdgeInsets.all(20.0),
child: Text(snapshot.hasData ? '${snapshot.data}' : ''),
);
},
)
],
),
),
);
}
#override
void dispose() {
widget.channel.sink.close();
super.dispose();
}
}
Am I doing something wrong, due to which the data isn't showing up?
Photo of flutter page:
I am new to flutter and developing an app but I am facing an issue regarding a post request to the asp.net core API method.
This below is the flutter code that I am using to post data to the asp.net core API method.
Future registerUser(
String userFullName, String username, String email, String password) async {
var body = jsonEncode({
"UserFullName": userFullName,
"Username": username,
"Email": email,
"Password": password
});
final response = await http.post(
Uri.parse(GetURI.baseURI() + 'Account/Register'),
body: body,
headers: {"content-type": "application/json"});
print("Hello Body! " + response.statusCode.toString());
print(response.body);
return null;
}
On button click, I am calling the above function to post data but getting an error. The code for the button click is below:
onPressed: () async {
final validationSuccess = _formKey.currentState!.validate();
if (validationSuccess) {
_formKey.currentState!.save();
final formData = _formKey.currentState!.value;
final userData = await registerUser(
_formKey.currentState!.fields['username']!.value,
_formKey.currentState!.fields['email']!.value,
_formKey.currentState!.fields['email']!.value,
_formKey.currentState!.fields['password']!.value);
print(userData!.userFullName);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('$formData'),
duration: Duration(seconds: 5),
));
}
}
On clicking the button it gives me the error like below:
[ERROR:flutter/lib/ui/ui_dart_state.cc(199)] Unhandled Exception: HandshakeException: Handshake error in client (OS Error:
CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate(handshake.cc:359))
This is the Class code from where I am returning my URL:
class GetURI {
static String baseURI() {
return 'https://10.103.78.29:44318/api/';
}
}
My main.dart file
import 'dart:io';
import 'package:bugsmashmobileapp/Screen/LoginScreen/login_body.dart';
import 'package:bugsmashmobileapp/Screen/SignupScreen/signup_body.dart';
import 'package:bugsmashmobileapp/Screen/WelcomeScreen/getting_started_screen.dart';
import 'package:flutter/material.dart';
class MyHttpOverrides extends HttpOverrides {
#override
HttpClient createHttpClient(SecurityContext? context) {
return super.createHttpClient(context)
..badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
}
}
void main() {
HttpOverrides.global = new MyHttpOverrides();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'BUGSMASH APP',
theme: ThemeData(
primarySwatch: Colors.blue, scaffoldBackgroundColor: Colors.white),
home: GettingStartedScreen(),
routes: {
SignupScreen.routeName: (context) => SignupScreen(),
LoginScreen.routeName: (context) => LoginScreen()
},
);
}
}
I have tried many ways and searched a lot but not able to get a good way to solve this error. If anyone can help it would be appreciated.
When I tried ngrok the problem is solved for me.
First I created an account on ngrok (Click here to go to website) and then you need to install its software to generate a URL and forwarding it to your API Project. Then you can use that URL in your Flutter Project without any issue.
I wrote a wss server in Nodejs and now I'm trying to connect to such server using Flutter.
Here's the code in NodeJS:
//Dependencies
const WebSocket = require('ws');
const fs = require('fs');
const https = require('https');
//Dependencies
//Server declarations
const server = https.createServer({
key: fs.readFileSync('pathTo/key.pem'),
cert: fs.readFileSync('pathTo/cert.pem')
});
server.listen(xxxx);
const wss = new WebSocket.Server({ server });
//Server declarations
wss.on('connection', function connection(ws)
{
ws.on('message', function incoming(message)
{
console.log('Received: ' + message);
ws.send('echo: ' + message);
});
ws.send('Connected!');
});
Here's the code in Flutter:
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:web_socket_channel/io.dart';
import 'package:connectivity/connectivity.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
class MyApp extends StatelessWidget
{
#override
Widget build(BuildContext context)
{
final title = 'LumenApp Prototype';
IOWebSocketChannel channel;
try
{
channel = new IOWebSocketChannel.connect('wss://xxxxxxxx.xxx.xxx:xxxx/');
MyHomePageState.noResponse = false;
}
catch(e)
{
MyHomePageState.noResponse = true;
}
return MaterialApp(
title: title,
theme: ThemeData(
primarySwatch: Colors.blue,
primaryTextTheme: TextTheme(
title: TextStyle(
color: Colors.yellow[600],
),
),
),
home: MyHomePage(
title: title,
channel: channel,
),
);
}
}
The error on Flutter is: WebSocketChannelException: WebSocketChannelException: HandshakeException: Handshake error in client (OS Error: CERTIFICATE_VERIFY_FAILED: self signed certificate(handshake.cc:354))
This happens inside this function:
void initPlatformState()
{
widget.channel.stream.listen((message)
{
setState(() { noResponse = false; });
//Handle message...
},
onError: (error)
{
print(error);
if(mounted)
{
setState((){ noResponse = true;});
}
},
onDone: ()
{
if(mounted)
{
setState((){ noResponse = true; });
}
});
}
I used a self-signed certificate server-side made with openssl.
Any idea how to solve this?
If you happen to bumped in this GitHub post, you can follow the temporary fix from this comment:
class MyHttpOverrides extends HttpOverrides{
#override
HttpClient createHttpClient(SecurityContext context){
return super.createHttpClient(context)
..badCertificateCallback = (X509Certificate cert, String host, int port)=> true;
}
}
void main(){
HttpOverrides.global = new MyHttpOverrides();
runApp(new MyApp());
}
It works on local ip with self signed certificate.
To elaborate, here is the same solution:
Just for the sake of clarity specially for the newcomers to
Flutter/Dart, here is what you need to do in order to enable this
option globally in your project:
In your main.dart file, add or import the following class:
HttpClient createHttpClient(SecurityContext? context){
return super.createHttpClient(context)
..badCertificateCallback = (X509Certificate cert, String host, int port)=> true; } } ```
In your main function, add the following line after function definition:
HttpOverrides.global = MyHttpOverrides();
This
comment was very helpful to pass through this matter, and please
note that...
This should be used while in development mode, do NOT do this when
you want to release to production, the aim of this answer is to make
the development a bit easier for you, for production, you need to fix
your certificate issue and use it properly, look at the other answers
for this as it might be helpful for your case.
Another thing worth mentioning, signed certificates are available for free now (https://letsencrypt.org/).
Also, I think the Flutter team is working to enhance the documentation for better reference regarding this issue. It is being tracked here.
I want to disable logging of Firebase Analytics in a Flutter project when the app is being run on Firebase Test Lab. According to Firebase docs, TestLab can be detected by adding the following in MainActivity.java
String testLabSetting = Settings.System.getString(getContentResolver(), "firebase.test.lab");
if ("true".equals(testLabSetting)) {
// Do something when running in Test Lab
// ...
}
How can I access the result of this test on the dart side in main.dart which is where I want to disable logging (as there are some other reasons logging is disabled already in the dart code).
Thanks!
I just found this. I didn't try it yet though:
https://pub.dev/packages/flutter_runtime_env
This project allows you to check if you're running in the Firebase
Test Labs
You can use it like their example:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_runtime_env/flutter_runtime_env.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool _shouldBeEnabled = false;
#override
void initState() {
super.initState();
initPlatformState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
var result = await shouldEnableAnalytics();
setState(() {
_shouldBeEnabled = result;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Should Enable Analytics'),
),
body: Center(
child: Text('Should Analytics be Enabled: $_shouldBeEnabled\n'),
),
),
);
}
}
EDIT:
I think I found a better solution.
https://pub.dev/packages/flutter_sentry
It has the follow method
/// Return `true` if running under Firebase Test Lab (includes pre-launch
/// report environment) on Android, `false` otherwise.
static Future<bool> isFirebaseTestLab() async
It seems to be the best solution so far...
EDIT 2:
Fuck it! I just created a small plugin.
https://pub.dev/packages/is_firebase_test_lab_activated