Ask user to connect to a specific WiFi flutter - flutter

I need to connect the user's device to a specific WiFi. I have used some of packages like wifi_iot but According to google, In Android API Version > 29, We can not directly connect the user device to a specific WiFi.
Instead google suggests that using WifiNetworkSuggestion
I have 2 options :
Use MethodChannel in flutter and implement WifiNetworkSuggestion in Kotlin and use it in my project.
Inform user to connect to my specific wifi manually(and of course I have to show the password to user).
Which of these options you recommend?
and I don't have any experience on implementing option 1. is It possible to implement something like this in flutter project ?
And if you can come up with another option, I would be glad to share it with me.

For anyone who want to Implement something like this in flutter : Before Android API Version 29, you can directly connect user's device to specific WiFi without any problem. but in API Version after 29 (Android >= 10), you cannot do this. you have to ask user if he/she wants to connect to your WiFi. So for example in flutter (note: you can not ask user to connect to wifi directly in dart code. so you have to write your logic in Kotlin or Java) you have to define Platform and configure channel to run kotlin or java code. (Writing custom platform-specific code
After that you can have something like this :
import io.flutter.embedding.android.FlutterActivity
import androidx.annotation.NonNull
import android.net.wifi.WifiNetworkSpecifier
import android.net.wifi.WifiConfiguration
import android.net.wifi.WifiManager
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import android.os.PatternMatcher
import android.net.NetworkRequest
import android.net.NetworkCapabilities
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.util.Log
class MainActivity: FlutterActivity() {
private val CHANNEL = "channelname/wifiworks"
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
call, result ->
if (call.method == "getTest") {
val temp = getTest(call.argument<String>("SSID").toString())
result.success(temp)
// Note: this method is invoked on the main thread.
// TODO
}
if(call.method == "dc") {
val temp = disconnect()
result.success(temp)
}
}
}
private fun disconnect(): Int {
if(android.os.Build.VERSION.SDK_INT >= 29)
{
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivityManager.unregisterNetworkCallback(mNetworkCallback)
}
return 3434;
}
private val mNetworkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
//phone is connected to wifi network
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivityManager.bindProcessToNetwork(network)
}
}
private fun getTest(ssid: String): Int {
if(android.os.Build.VERSION.SDK_INT >= 29)
{
val specifier = WifiNetworkSpecifier.Builder()
// .setSsidPattern(PatternMatcher("SSID", PatternMatcher.PATTERN_PREFIX))
.setSsid(ssid)
.setWpa2Passphrase("Your_Password")
.build()
val request = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.setNetworkSpecifier(specifier)
.build()
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivityManager.requestNetwork(request, mNetworkCallback)
// Release the request when done.
//
return 123
}
else {
var networkSSID = ssid;
var networkPass = "Your_Password";
var conf = WifiConfiguration()
conf.SSID = "\"" + networkSSID + "\""
conf.preSharedKey = "\""+ networkPass +"\""
var wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
var netid = wifiManager.addNetwork(conf)
wifiManager.disconnect()
wifiManager.enableNetwork(netid, true)
wifiManager.reconnect()
return ssid.length
}
}
}
Hope this is helpful ;)

Related

Have I to handle Random.secure() unsupported error in Flutter?

Is there some configuration/android version/phone model/etc. in which Random.secure() can throw an unsupported error for real?
https://api.dart.dev/stable/2.18.3/dart-math/Random/Random.secure.html
says:
"If the program cannot provide a cryptographically secure source of random numbers, it throws an UnsupportedError".
If you want to be sure, you'll have to take a look inside the code, wich extends the abstract class Random.
I found the following unit test in the dart sources:
https://github.com/dart-lang/sdk/blob/main/tests/lib/math/random_secure_unsupported_test.dart
// Test that `Random.secure()` throws `UnsupportedError` each time it fails.
import "package:expect/expect.dart";
import 'dart:math';
main() {
var result1 = getRandom();
var result2 = getRandom();
Expect.isNotNull(result1);
Expect.isNotNull(result2); // This fired for http://dartbug.com/36206
Expect.equals(result1 is Random, result2 is Random);
Expect.equals(result1 is UnsupportedError, result2 is UnsupportedError);
}
dynamic getRandom() {
try {
return Random.secure();
} catch (e) {
return e;
}
}
Calling Random.secure() two times in a row seems to cause an issue as described in http://dartbug.com/36206, but only for IE browser and Dart2JS.

embedding golang server with flutter

I have web server written in golang which uses graphql package gqlgen and gorm for database.
Since golang can be compiled and run on android I wanted to create offline version of my app where sqlite can be used for offline storage and import my whole server as an aar.
I have successfully built aar and add it on my flutter using gomobile by following instructions here
When I run my app, server is started on android it seems to work just fine and when opening http://localhost:8080/ on emulator's chrome app I am seeing graphql playground runs without any problem just like I see on browser in windows.
The only problem I face is that flutter app just shows a blank screen while server runs in background. The following are the logs printed when app is started
Launching lib\main.dart on sdk gphone64 x86 64 in debug mode...
Running Gradle task 'assembleDebug'...
√ Built build\app\outputs\flutter-apk\app-debug.apk.
Installing build\app\outputs\flutter-apk\app.apk...
Debug service listening on ws://127.0.0.1:62561/DyGpOhyuekw=/ws
Syncing files to device sdk gphone64 x86 64...
I/GoLog ( 6295): connect to http://localhost:8080/ for GraphQL playground
W/ux.offline( 6295): type=1400 audit(0.0:38): avc: denied { read } for name="somaxconn" dev="proc" ino=74990 scontext=u:r:untrusted_app:s0:c149,c256,c512,c768 tcontext=u:object_r:proc_net:s0 tclass=file permissive=0 app=com.nux.offline
I think maybe problem lies on the above logs avc: denied { read } for name="somaxconn" or something is causing the blocking of ui thread since its like flutter don't render a thing.
I am using flutter plugin to start server and this is ServerPlugin.kt
package com.mahesabu.server.server
import androidx.annotation.NonNull
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import lib.Lib.startServer
/** ServerPlugin */
class ServerPlugin : FlutterPlugin, MethodCallHandler {
/// The MethodChannel that will the communication between Flutter and native Android
///
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
/// when the Flutter Engine is detached from the Activity
private lateinit var channel: MethodChannel
override fun onAttachedToEngine(#NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "server")
channel.setMethodCallHandler(this)
}
override fun onMethodCall(#NonNull call: MethodCall, #NonNull result: Result) {
if (call.method == "startServer") {
try {
val port = startServer() //from golang bindings
result.success(port)
} catch (e: Exception) {
e.printStackTrace();
result.error("Error in starting server", "${e.message}", null);
}
} else {
result.notImplemented()
}
}
override fun onDetachedFromEngine(#NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
}
And this is is dart code
class Server {
static const MethodChannel _channel = MethodChannel('server');
static Future<String?> startServer() async {
try {
final String? port = await _channel.invokeMethod('startServer');
return port;
} catch (e) {
log('startServer error: ${e.toString()}');
return null;
}
}
}
and my app's main is as follows
import 'package:flutter/material.dart';
import 'package:server/server.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final port = await Server.startServer(); //problem
print('port $port'); //This don't print
runApp(const MyApp());
}
On go side this how i start server
//start.go
package util
import (
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/playground"
"log"
"my-project/graph"
"my-project/graph/generated"
"net/http"
"os"
)
const defaultPort = "8080"
// StartServer This way so that it can be invoked via libs
func StartServer(Offline bool) string {
port := os.Getenv("PORT")
if port == "" {
port = defaultPort
}
db := InitDB(Offline)
config := generated.Config{Resolvers: &graph.Resolver{
DB: db,
}}
srv := handler.NewDefaultServer(generated.NewExecutableSchema(config))
http.Handle("/", playground.Handler("GraphQL playground", "/query"))
http.Handle("/query", srv)
log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)
log.Fatal(http.ListenAndServe(":"+port, nil))
return port
}
and this is lib for generating bindings with gomobile
//lib.go
package lib
import "my-project/util"
// StartServer This way so that it can be invoked via libs
func StartServer() string {
return util.StartServer(true)
}
Any help on fixing this will be appreciated.
Edit
I think problem occurs when embedded server tries to create a new port. I don't know if it is possible for an app to open a new port in android just like nodejs, golang open things like http://localhost:8080.
Now I think if there is a way to create port then I can run my app successfully but I don't know how exactly.
I was thinking if I can find any available port on android and use to start server maybe this stack could be possible. In kotlin something like this may work in finding port.
import java.net.ServerSocket
fun main() {
val serverPort = ServerSocket(0)
print(serverPort.toString())
}
but it crashes on android app when I try similar thing.
I have uploaded a repository on GitHub showing what I intend to do. It's just a simple golang server using gin and android app (with no flutter) it is available here.
"I don't know if it is possible for an app to open a new port in android just like nodejs, golang open things like localhost:8080"
To find out the root cause, try to run an HTTP server in android, such as How to create a HTTP server in Android?. If that succeeds, try to find the differences about how they deal with ports.
In addition, please be sure you have correct permission in androidmanifest.xml.
(Rephrased from my comments)

Invoke a method in flutter after restarting the device

I build a to-do list app that should show notifications to remind about tasks. In order to be able to schedule the notifications to the exact minute of the deadline, I pass the notifications data from flutter to kotlin, and showing the notification from a Broadcast receiver.
Here I am sending the notifications data to kotlin:
await platform.invokeMethod('setNextNotification', {
'tasksNames': tasksNames,
'notificationsTimeInMillis': notificationsTimeInMillis
});
This is how I get the data inside FlutterActivity:
private const val CHANNEL = "flutter.native/helper"
class MainActivity : FlutterActivity() {
companion object {
const val TASKS_NAMES_EXTRA = "tasksNames"
const val NOTIFICATIONS_TIME_IN_MILLIS_EXTRA = "notificationsTimeInMillis"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this)
// Init the AlarmManager.
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
// We got here from the setNotifications() method in flutter...
MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == "setNextNotification") {
// Get the time till next notification
val notificationsTimeInMillis: ArrayList<Long> = call.argument(NOTIFICATIONS_TIME_IN_MILLIS_EXTRA)
?: ArrayList()
// Create a pending intent for the notifications
val pIntent: PendingIntent? = createPendingIntent(call.argument(TASKS_NAMES_EXTRA), call.argument(TIME_LEFT_TEXTS_EXTRA), notificationsTimeInMillis, this)
// Cancel all alarms
while (alarmManager.nextAlarmClock != null)
alarmManager.cancel(alarmManager.nextAlarmClock.showIntent)
// Set the alarm
setAlarm(notificationsTimeInMillis[0], pIntent, alarmManager)
}
}
}
private fun setAlarm(notificationTime: Long, pIntent: PendingIntent?, alarmManager: AlarmManager) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // The API is 23 or higher...
alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(notificationTime, pIntent), pIntent)
} else { // The API is 19 - 22...
// We want the alarm to go of on the exact time it scheduled for so we use the setExact method.
alarmManager.setExact(AlarmManager.RTC_WAKEUP, notificationTime, pIntent)
}
}
private fun createPendingIntent(tasksNames: ArrayList<String>?, timeTillNotificationsInMillis: ArrayList<Long>?,
context: Context): android.app.PendingIntent? {
return try {
val intent: android.content.Intent = android.content.Intent(context, AlarmManagerHelperWakeful::class.java)
intent.action = "notification"
intent.putStringArrayListExtra(TASKS_NAMES_EXTRA, tasksNames)
intent.putStringArrayListExtra(NOTIFICATIONS_TIME_IN_MILLIS_EXTRA, timeTillNotificationsInMillisAsString)
android.app.PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
} catch (e: java.lang.Exception) {
null
}
}
}
And this is how I show the notification on the BroadcastReceiver, and after that sets the next notification:
Class AlarmManagerHelperWakeful : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent != null && intent.action == "notification" && context != null) {
val tasksLabels: ArrayList<String> = intent.getStringArrayListExtra(MainActivity.TASKS_NAMES_EXTRA)
?: ArrayList()
val notificationsTimeInMillisAsString: ArrayList<String> = intent.getStringArrayListExtra(MainActivity.NOTIFICATIONS_TIME_IN_MILLIS_EXTRA)
?: ArrayList()
if (tasksLabels.size > 0) {
// Create a notification manager.
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
var builder = NotificationCompat.Builder(context) // The initialization is for api 25 or lower so it is deprecated.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // This is API 26 or higher...
// Create a channel for API 26 or higher;
val channelId = "channel_01" // The id of the channel.
if (notificationManager.getNotificationChannel(channelId) == null) {
val channel = NotificationChannel(channelId,
context.getString(R.string.notification_channel_name),
NotificationManager.IMPORTANCE_DEFAULT)
notificationManager.createNotificationChannel(channel)
}
// Update the builder to a no deprecated one.
builder = NotificationCompat.Builder(context, channelId)
}
// Set the notification details.
builder.setSmallIcon(android.R.drawable.ic_notification_overlay)
builder.setContentTitle(tasksLabels[0])
builder.setContentText(someText)
builder.priority = NotificationCompat.PRIORITY_DEFAULT
notificationId = someUniqueId
// Show the notification.
notificationManager.notify(notificationId.toInt(), builder.build())
// Remove this notification from the notifications lists.
tasksLabels.removeAt(0)
notificationsTimeInMillisAsString.removeAt(0)
// There are more notifications...
if (tasksLabels.size > 0) {
// Init the AlarmManager.
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
// Cancel all alarms
while (alarmManager.nextAlarmClock != null)
alarmManager.cancel(alarmManager.nextAlarmClock.showIntent)
// Create a pending intent for the notifications
val pIntent: PendingIntent? = createPendingIntent(tasksLabels, cnotificationsTimeInMillisAsString, context)
// Set the alarm
setAlarm(notificationsTimeInMillisAsString[0].toLong(), pIntent, alarmManager)
}
}
} else {
if (intent == null) {
Log.d("Debug", "Checking: intent == null")
} else if ( intent.action != "notification") {
Log.d("Debug", "Checking: intent.action != notification")
val tasksLabels: ArrayList<String> = intent.getStringArrayListExtra(MainActivity.TASKS_NAMES_EXTRA)
?: ArrayList()
Log.d("Debug", "Checking: tasksNames.size inside else if" + tasksLabels.size)
}
}
}
}
Everything works perfectly fine unless I restart my device. Then the Broadcast receiver gets an intent without any data. For the BoradcastReceiver to get intent with notifications data, I need to invoke the method from the flutter code (the method that sends notifications data to the kotlin code), which means that for now, the user has to enter the app for that. Otherwise, user will not see notifications until entering my app and reinvoking the flutter code.
How can I overcome that issue?
You should use Push Notification instead of sending local notification to your broadcast receiver. There're lot of cases that make your app cannot send local notification. ex: user closed app (lot of users alway close app after using), OS closed app or clean memory, Dart method is crashing.
Firebase FCM is pretty simple, it's much more simple than you solution that's using broadcast receiver. Also totally free.
https://pub.dev/packages/firebase_messaging
Pushwoosh is good too, it has schedule notification
https://pub.dev/packages/pushwoosh
Using push notification also has other pros that you app will also work on iOS, doesn't need to keep your app running on background which is very bad idea if your app doesn't have any special features that need to run background (music player, geo location, VOIP)
In case you don't want to use Push Notification. Take a look at this lib:
https://pub.dev/packages/flutter_local_notifications
Right now you are sending data from dart to the native plugin. You might try it the other way around. This example shows how you can get the native android reboot event. Next, you can use this example to fetch the required data. After fetching the data you can set the notification.
You could also try to store the information of the last notification in SharedPreferences, fetch that on boot, and set the notification.

How to pass session values to a method feed() in gatling correctly

I have the following problem. When I try to execute the simulation, I get this error:
Generating reports...
Exception in thread "main" java.lang.UnsupportedOperationException: There were no requests sent during the simulation, reports won't be generated
at io.gatling.charts.report.ReportsGenerator.generateFor(ReportsGenerator.scala:49)
at io.gatling.app.RunResultProcessor.generateReports(RunResultProcessor.scala:62)
at io.gatling.app.RunResultProcessor.processRunResult(RunResultProcessor.scala:40)
at io.gatling.app.Gatling$.start(Gatling.scala:88)
at io.gatling.app.Gatling$.fromMap(Gatling.scala:41)
at Engine$.delayedEndpoint$Engine$1(Engine.scala:11)
at Engine$delayedInit$body.apply(Engine.scala:4)
at scala.Function0.apply$mcV$sp(Function0.scala:39)
at scala.Function0.apply$mcV$sp$(Function0.scala:39)
at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:17)
at scala.App.$anonfun$main$1$adapted(App.scala:80)
at scala.collection.immutable.List.foreach(List.scala:392)
at scala.App.main(App.scala:80)
at scala.App.main$(App.scala:78)
at Engine$.main(Engine.scala:4)
at Engine.main(Engine.scala)
Process finished with exit code 1
Below is my code:
package simulations
import io.gatling.core.Predef._
import io.gatling.core.scenario.Simulation
import io.gatling.http.Predef._
class Load extends Simulation{
val httpProtocol = http
.baseUrl("http://localhost:8080/app/")
.header("Accept", "application/json")
val scn = scenario("Scenario").exec(SimpleExample.simple)
setUp(
scn.inject(atOnceUsers(3)).protocols(httpProtocol)
)
}
package simulations
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.util.Random
object SimpleExample {
var simple =
exec(session => session
.set("rndmSTR", randomString())
.set("rndmINT", randomInt())
).
exec(
session => {
exec(feed(Iterator.continually(Map(
"game_ID" -> session("rndmINT").as[String].toInt,
"game_Name" -> session("rndmSTR").as[String]
))))
.exec(
http("Post New Game")
.post("videogames/")
.body(ElFileBody("bodies/newtemplate.json")).asJson
)
session
}
)
private def randomString() = {
new Random().alphanumeric.filter(_.isLetter).take(5).mkString.toLowerCase
}
private def randomInt() = {
new Random().nextInt(100000)
}
}
This is my .json:
{
"id": "${game_ID}",
"name": "${game_Name}",
"releaseDate": "2020-08-11",
"reviewScore": 99,
"category": "Driving",
"rating": "Mature"
}
I know that i can use the feed() method as follows:
package simulations
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.util.Random
object NextSimpleExample {
var rndName: String = null
var rndID: String = null
var feeder = Iterator.continually(Map(
"game_ID" -> rndID.toInt,
"game_Name" -> rndName
))
var simple =
exec(session => session
.set("rndmSTR", randomString())
.set("rndmINT", randomInt())
).
exec(
session => {
rndID = session("rndmINT").as[String]
rndName = session("rndmSTR").as[String]
session
}
)
.exec(feed(feeder)
.exec(
http("Post New Game")
.post("videogames/")
.body(ElFileBody("bodies/newtemplate.json")).asJson)
)
private def randomString() = {
new Random().alphanumeric.filter(_.isLetter).take(5).mkString.toLowerCase
}
private def randomInt() = {
new Random().nextInt(100000)
}
}
but in this case, all virtual users will get similar values...
Also, i want to use generated for each virtual user values in next steps. For example, insert generated id & name in another .json file for another body in post or put request.
Please, help to resolve this problem.
The gatling DSL defines builders that are created once, so any kind of reference code like
exec(session => session
.set("rndmSTR", randomString())
.set("rndmINT", randomInt())
)
will result in the same values for all users.
Your second example is really a convoluted way of restating the first by way of transferring to plain scala variables - you're still only ever running your 'random' functions once.
But you were close - if you move your random functions into the feeder definition, it will work because the functions will be evaluated each time .feed gets called.
var feeder = Iterator.continually(Map(
"game_ID" -> randomString(),
"game_Name" -> randomInt()
))
So you don't need the session functions at all

cannot get rest service

This drives me crazy, I have 2 http requests in a sample application:
open class RestController : Controller() {
val api = Rest()
init {
api.baseURI = "http://127.0.0.1:5059/"
}
}
class PendingCtlr : RestController() {
fun load(): ObservableList<PendingEntity> {
val txt = api.get("pendings").list()
val temp = txt.toModel<PendingEntity>()
return temp.observable()
}
}
class ConfirmedCtrl : RestController() {
fun load(id: Long): ObservableList<ConfirmedEntity> {
val li= api.get("confirmeds").list()
val temp = li.toModel<ConfirmedEntity>()
return temp.observable()
}
}
The first one works, the second one doesn't even hit the application level, it gets rejected with 400 BadRequest by my backend (Werkzeug).
I see absolutely no difference in both functions, and I can call both routes from my Swagger, as well as from python as well as from curl! Could someone please advise at least where to look for debug?
EDIT: The problem was on server side -_- Solved