How to stop Flutter Web HtmlElementView from rebuilding/rerunning? - flutter

I have built a video calling app in Flutter using the Agora SDK. This is only available for iOS/Android and so for the web build I had to build a wrapper around the existing Agora web SDK. Due to the way Flutter renders web elements inside a shadow DOM, you cannot access elements by document.getElementById(), which is what the Agora SDK uses to inject their video player. To get around this I am rendering an IFrame that has the div and Agora SDK script bundled together.
It is all working nicely but when any event is triggered inside the browser window, such as the mouse entering a button or clicking anything, the IFrame refreshes and rebuilds the video view which takes 1-2 seconds to initialize.
Is there anyway I can unlink the IFrame from the browser events? I tried marking the HtmlElementView as const and putting it inside a StatefulWidget that only registers the platform view once. The widget isn't actually running the build() method again but the IFrame still refreshes.
Flutter code
ui.platformViewRegistry.registerViewFactory(
'video-container',
(int viewId) => IFrameElement()
..id = 'my-iframe'
..width = '100%'
..height = '100%'
..src = 'assets/web_elements/agora_container.html'
..allow = "camera; microphone"
..style.border = 'none');
#override
Widget build(BuildContext context) {
print("*****\n\n\nBuilding the web host container\n\n\n*****"); // this is only printing once
return const HtmlElementView(
viewType: 'video-container',
);
}
Agora code
<div id="local-video"></div>
<div id="remote-video"></div>
<div id="video-canvas"></div>
<script src="scripts/AgoraSDK.js"></script>
<script src="scripts/agora_controller.js"></script>

What worked for me was this:
Key key = UniqueKey(); // before the build method
HtmlElementView(
key: key,
viewType: 'iframeElement',
);
Now there is no rebuilding/rerunning. Note I am using the iframeElement, not video-container, but this should work. Please comment.

This is one of the active problem with the Flutter Web. I guess a workaround to this is mentioned here: https://github.com/flutter/flutter/issues/53253#issuecomment-607197549

Related

How can I get the details of currently playing song in miniplayer?

I am working on a music player app in flutter.
I fetch the songs from devices using on_audio_query package and display them in listview. When I click on a song, i set the plylist of all songs and play the one thats clicked using just_audio package like this.
await player.setAudioSource(
ConcatenatingAudioSource(
useLazyPreparation: true,
shuffleOrder: DefaultShuffleOrder(),
children: allSongsList! // allSongsList is the list of
songs(on_audio_query)
.map((songModel) =>
AudioSource.uri(Uri.parse(songModel.data)))
.toList(),
),
initialIndex: index, // Listview Index
initialPosition: Duration.zero);
await item.player.play();
I want to show miniplayer at the bottom only when there is a song playing(paused),refresh the song descriptions,how do I get the song description(Artist/song)?
I will let you the implementation of the mini player widget for you, however, I will consider that I have a shouldMiniPlayerBeVisible, in the Scaffold of your page:
First, declare and initialize a shouldMiniPlayerBeVisible variable that will manage the show/hide of the mini player:
bool shouldMiniPlayerBeVisible = false;
and a currentAudio which will take the song and pass it in the mini player.
YourAudioModel currentAudio = YourAudioModel();
Here, I supposed that I have a simple YourAudioModel which will hold information about audio such as title, description, length, URL (or local) path ...
I recommend not setting it to null initially , you can initialize it with a placeholder YourAudioModel audio model that has some dummy data, or loading data information for example...
now in your Scaffold:
Scaffold(
floatingActionButton: Visibility(
visible: shouldMiniPlayerBeVisible,
child: shouldMiniPlayerBeVisible(songToPlay: currentAudio),
),
),
I will consider that you are using a StatefulWidget, but the steps are the same as you need just to get the idea of it to implement it in other state management solutions.
in the initState, you need to listen to the playerStateStream stream like this:
player.playerStateStream.listen((state) {
if (state.playing) {
shouldMiniPlayerBeVisible = true;
} else {
shouldMiniPlayerBeVisible = false;
}
setState(() {});
});
this basically will track if any audio is playing or not, then show the mini player based on it.
now before your run the await item.player.play();, set your currentAudio variable to the new audio which is running, you can do it with a function like this:
Future<void> playCurrentAudioANdShowItInMiniPlayer(YourAudioModel audio) async {
currentAudio = YourAudioModel;
await item.player.play();
}
now from your UI, for each item in ListView, you can just call the playCurrentAudioANdShowItInMiniPlayer method with its YourAudioModeland expect it to work fine.
from here, you should implement the actual mini player widget, and you will need also a custom Navigator widget so that mini player will be on the screen even if you navigated to other screens, well I faced this issue before and the miniplayer worked just really fine and good as I expected, I recommend you to use it.

How to add back button to WebView in Flutter to dismiss the WebView altogether?

Currently when I tap on an image, it should open a web view as the image object has an associated url. I am using the url_launcher library for Flutter, and I have implemented the code as follows:
onTap: () async {
final url = image.url;
if (await canLaunch(url)) {
await launch(
url,
forceSafariVC: true,
forceWebView: true,
enableJavaScript: true,
);
}
},
My understanding is that this launches a WebView, which is an in-app browser rather than taking the user out of the app and into a separate browser app. This works fine, but the page loads much slower and Javascript elements do not work properly (e.g. animated text on websites). On the other hand,
onTap: () async {
final url = banners[index].url;
if (await canLaunch(url)) {
await launch(
url
);
}
},
works much better as it is faster and everything loads in properly, but it opens an external browser, which is not what I want.
Another issue is that I would like to add a Done button on the top left corner of the WebView to completely exit the WebView and return to where I was in the app. I only have a back button on the bottom left (on the Android emulator), that lets me go to the previous page I was at in the browser.
How do I customise the layout of the WebView, if I do not have any access to it? The url_launcher seems to handle the WebView creation internally, so I'm wondering how can I gain access from the above code to add that button?
Thank you!
If you use the native webview in this manner then you can't customise the ui. But instead, since you are displaying image you can use image.network from flutter.
Image.network(imgURL,fit: BoxFit.fill,
loadingBuilder:(BuildContext context, Widget child,ImageChunkEvent loadingProgress) {
if (loadingProgress == null)
return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null ?
loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes
: null,
),
);
},
),
Or, you can also use official webview plugin from flutter team to create in app web view.
webview_flutter: ^2.3.1
In this approaches if you add a back button in ui you can the use Navigator.pop(context); on onPressed property in button to go back

Flutter Google Maps blank on app resume with multiple pages displaying map

I have an issue with showing GoogleMaps in my Flutter app, I've implemented it in a swiper, so each page should show the location with a custom marker, would post a code sample but it's too long so I'll try to explain it as short as I can.
Basically when I open the swiper with detail pages in it, it has a map in the bottom and it shows fine while swiping through the pages, but if I put the app in background and get back to it after few swipes it shows just white container with Google logo but no map.
It happens on physical devices but not on the Emulator.
Found the issue opened here
Also tried suggestions from this question
If anyone has an idea on how to fix it, it would be appreciated
I had this issue but didn't realize it was a reported bug. Trying to track down the problem I tried a brute-force mark-the-whole-UI-dirty on foregrounding to see if might be a redraw issue and it stopped happening. Not elegant but it seems to work. None of our testers have reported the blank Google Maps since. I also found I had to do it once Flutter was ready to build() after foregrounding.
This is in the stateful widget that is the first child of my MultiProvider at the top of the app. The google map is in a Scaffold()->Stack() farther down the graph:
#override
void didChangeAppLifecycleState(AppLifecycleState state) async {
if(state == AppLifecycleState.resumed) {
setState(() { _refreshAllChildrenAfterWakeup = true; });
}
}
void _rebuildAllChildren(BuildContext context) {
void rebuild(Element el) {
el.markNeedsBuild();
el.visitChildren(rebuild);
}
(context as Element).visitChildren(rebuild);
}
Widget build(BuildContext context) {
if(_refreshAllChildrenAfterWakeup == true) {
_refreshAllChildrenAfterWakeup = false;
_rebuildAllChildren(context);
}
//...
}

How to implement reCaptcha into a flutter app

I am trying to implement the reCaptcha function to my flutter app, but in the captcha registration I need to provide a domain which I don't have one for a mobile app. I have browsed several guides teaching how to implement reCaptcha into a mobile app, yet those guides registered their reCaptcha with package names but not domains. What is the correct way to implement reCaptcha in a flutter app, or any mobile app in 2020?
You can use this plugin, flutter_recaptcha.
For the domain, I had the same issue. I first found that I needed to use the "I'm not a robot" checkbox option from here and I had to check the github repository to find this information, "!!! Remember to add this domain into the reCaptcha setting: recaptcha-flutter-plugin.firebaseapp.com," which explains it.
I was lost for a bit after not seeing that on the main page, but now it makes sense. Hopefully it helps.
Edit
I noticed something after trying it out, that I'd like to mention. The plugin does not provide a captcha response for using to authenticate the user server-side, so it does not seem very useful as it is. However, it is a simple plugin, so it may be possible to use it as an example. The steps, I think, would be to create a webpage with the captcha. As with the plugin, use a webview to open the page, then capture the post output of the form and ip address of user submitting the form, using something like this, then send it to flutter and then submit your request with that information, and use the Google library to verify the captcha.
Instructions
I just finished implementing this and I found a good way that works.
First, create an html page, like this:
<html>
<head>
<title>reCAPTCHA</title>
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
</head>
<body style='background-color: aqua;'>
<div style='height: 60px;'></div>
<form action="?" method="POST">
<div class="g-recaptcha"
data-sitekey="YOUR-SITE-KEY"
data-callback="captchaCallback"></div>
</form>
<script>
function captchaCallback(response){
//console.log(response);
if(typeof Captcha!=="undefined"){
Captcha.postMessage(response);
}
}
</script>
</body>
</html>
Then, host that on your domain, say example.com/captcha.
Then, create a flutter Widget, like this:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class Captcha extends StatefulWidget{
Function callback;
Captcha(this.callback);
#override
State<StatefulWidget> createState() {
return CaptchaState();
}
}
class CaptchaState extends State<Captcha>{
WebViewController webViewController;
#override
initState(){
super.initState();
}
#override
Widget build(BuildContext context) {
return Center(
child: WebView(
initialUrl: "https://example.com/captcha.html",
javascriptMode: JavascriptMode.unrestricted,
javascriptChannels: Set.from([
JavascriptChannel(
name: 'Captcha',
onMessageReceived: (JavascriptMessage message) {
//This is where you receive message from
//javascript code and handle in Flutter/Dart
//like here, the message is just being printed
//in Run/LogCat window of android studio
//print(message.message);
widget.callback(message.message);
Navigator.of(context).pop();
})
]),
onWebViewCreated: (WebViewController w) {
webViewController = w;
},
)
);
}
}
Make sure you registered for a captcha key at https://www.google.com/recaptcha (click on "Admin Console" at the top-right).
Then, you have the front-end built. To call a captcha, just run:
Navigator.of(context).push(
MaterialPageRoute(
builder: (context){
return Captcha((String code)=>print("Code returned: "+code));
}
),
);
You can use whatever callback you want to, like this:
class GenericState extends State<Generic>{
void methodWithCaptcha(String captchaCode){
// Do something with captchaCode
}
#override
Widget build(BuildContext context) {
return Center(child:FlatButton(
child: Text("Click here!"),
onPressed: (){
Navigator.of(context).push(
MaterialPageRoute(
builder: (context){
return Captcha(methodWithCaptcha);
}
),
);
}
}
}
Server-side, you can follow the instructions here (I followed the sections "Direct Download" and "Usage"). I found that for the usage, I could simply use the code:
$recaptcha = new \ReCaptcha\ReCaptcha($secret);
$resp = $recaptcha->verify($gRecaptchaResponse, $remoteIp);
if ($resp->isSuccess()) {
// Verified!
} else {
$errors = $resp->getErrorCodes();
}
Using setExpectedHostname, like in the example, was unnecessary.
After that, everything works! I think this is currently the best way to implement Google reCaptcha V2 in flutter (for both iOS and Android).
If you are looking for Flutter WEB, You can try g_recaptcha_v3 package
Note:
it supports reCAPTCHA V3 only and not V2
its for Flutter Web only and no other platform supports
I've improved #JVE999 approach and created a new package:
flutter_firebase_recaptcha
The package uses InAppWebView for recapcha HTML rendering, so you don't need separate web page anymore.
My widget supports 'visible' and 'invisible' recapcha. Invisible recapcha allows you try to get recapcha token without showing anything to your user.

I can't use svg on flutter web how can i do it?

Hi i cant seem to get svg images to showup on flutter web
i saw two questions on stackoverflow with green tick to the answer that it says
we can use Image.asset() as svg container but it doesn't work
what is the solution
Until there is a proper solution you can use a HtmlElementView to show a svg on flutter web.
HtmlElementView can be used like this:
HtmlElementView(
viewType: 'img-svg-${hashCode}',
)
You have to register your viewType first before you can use it:
platformViewRegistry.registerViewFactory('img-svg-${hashCode}',
(int viewId) {
final String base64 = base64Encode(utf8.encode(svgString));
final String base64String = 'data:image/svg+xml;base64,$base64';
final html.ImageElement element = html.ImageElement(
src: base64String, height: width.toInt(), width: width.toInt());
return element;
});
I created an example project that shows how to load svg from asset and how to do the web import dance if you want to use it for mobile and for web.