How to connect a Phantom wallet to my Flutter web app? - flutter

I've been trying unsuccesfully to connect a Flutter web app to a Phantom wallet. No pub.dev packages have been released in order to accomplish this and can't figure out how to do it with dart-js interop.
Wondering if someone already figured it out?

I have a (crude) working piece of code that could be useful for somebody trying to accomplish the same:
// web/index.html
<script src="../lib/wallet.js" />
// wallet.js
class ClientWallet {
constructor() {
this.pubKey = '';
}
async connect() {
const resp = await window.solana.connect();
this.pubKey = resp.publicKey.toString();
}
address() {
return this.pubKey;
}
disconnect() {
window.solana.disconnect();
}
}
var walletModule = { ClientWallet: ClientWallet };
// main.dart
import 'package:js/js.dart';
import 'package:js/js_util.dart';
#JS('walletModule.ClientWallet')
class ClientWallet {
external Future<void> connect();
external void disconnect();
external String get pubKey;
}
Future<void> connectWallet() async {
ClientWallet wallet = ClientWallet();
await promiseToFuture(wallet.connect());
}
And then for connecting simply call connectWallet(). This works for me for the Phantom wallet, now I'm trying to integrate the Solana Dart package for signing a transaction.

Related

Is there currently a way to compute a dart function with webworkers?

I am currently trying to run a A* seek computation on flutter web
class AStarSeeker extends Seeker {
#override
Future<Path> Function() seek(SeekComputationInput input) {
return () => compute(doComputation, input);
}
}
Future<Path> doComputation(SeekComputationInput input) async
{
return Path.empty;
}
But I read that compute() does not currently work on flutter web. Is there a way that I can run my dart function with a webworker? I read that you can run js functions but couldn't find anything about dart functions.
Sounds like you can use raw Isolates in Dart. Read the official doc about isolates, which are just like threads in other languages.
By the way this link may be helpful: Dart: in browser webworker written in dart and used in dart2js and https://api.dart.dev/stable/2.16.0/dart-html/Worker-class.html
UPDATE
Sounds like isolate does not work in web. Then what about the "Worker" approach I mentioned above. Notice that you can always compile your dart code to a js file, so even if Worker only supports js you can still execute dart there.
This https://dev.to/kyorohiro/isolate-at-flutter-for-web-28lg sounds like a good tutorial.
The core part:
parent.dart
import 'dart:html' as html;
main() async {
if(html.Worker.supported) {
var myWorker = new html.Worker("ww.dart.js");
myWorker.onMessage.listen((event) {
print("main:receive: ${event.data}");
});
myWorker.postMessage("Hello!!");
} else {
print('Your browser doesn\'t support web workers.');
}
}
child.dart
import 'dart:async';
import 'dart:html' as html;
import 'dart:js' as js;
import 'package:js/js.dart' as pjs;
import 'package:js/js_util.dart' as js_util;
#pjs.JS('self')
external dynamic get globalScopeSelf;
Stream<T> callbackToStream<J, T>(String name, T Function(J jsValue) unwrapValue) {
var controller = StreamController<T>.broadcast(sync: true);
js_util.setProperty(js.context['self'], name, js.allowInterop((J event) {
controller.add(unwrapValue(event));
}));
return controller.stream;
}
void jsSendMessage( dynamic object, dynamic m) {
js.context.callMethod('postMessage',[m]);
}
main() {
callbackToStream('onmessage', (html.MessageEvent e) {
return js_util.getProperty(e, 'data');
}).listen((message) {
print('>>> ${message}');
jsSendMessage(js.context, 'callback: ${message}');
});
}

Bundle.main.resourcePath returning nil in my flutter plugin, after working fine previously

I've made a test flutter app that uses a plugin I made that simply retrieves the path of the resources folder in my .app and returns it to my dart code.
This works completely fine in the example project generated with the plugin, and has previously worked with my app. I didn't use the plugin for a while after this and it went unused, up until recently as I now had an actual use for it so included it in my code once again. Now the plugin doesn't seem to work with any project I add it to, even freshly made ones.
My plugin consists of this dart code:
import 'dart:async';
import 'package:flutter/services.dart';
class MacBundleUtils {
static const MethodChannel _channel =
const MethodChannel('mac_bundle_utils');
static Future<String?> get getResourcesDir async {
final String? resDir = await _channel.invokeMethod('getResourcesDir');
return resDir;
}
}
and this swift code:
import Cocoa
import FlutterMacOS
public class MacBundleUtilsPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "mac_bundle_utils", binaryMessenger: registrar.messenger)
let instance = MacBundleUtilsPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: #escaping FlutterResult) {
switch call.method {
case "getResourcesDir":
result(Bundle.main.resourcePath)
default:
result(FlutterMethodNotImplemented)
}
}
}
I then try to retrieve this path and print it to the console with this one simple line:
print(await MacBundleUtils.getResourcesDir);
This however returns the following error:
Unhandled Exception: Null check operator used on a null value
What could I be doing wrong here?
Thanks.
I actually came across the solution shortly after posting this question.
I was trying to access Bundle.main too early as my code was in void main().
After moving my code to execute after loading with initState() like so:
#override
void initState() { //for linux app, tells user if permissions aren't correct
super.initState();
WidgetsBinding.instance!.addPostFrameCallback((_) async {
print(await MacBundleUtils.getResourcesDir);
});
}
it works with no issues.

How to Access Provider with out Context in Flutter

I have a problem with the Flutter Provider pattern, I need to access Provides from Class where I don't have Context.
Providers :
import 'package:flutter/foundation.dart';
class TokenProvider with ChangeNotifier {
TokenService tokenService = TokenService();
String _accessToken = '';
String get accessToken {
return _accessToken;
}
dynamic setAccessToken(data) async {
_accessToken = data;
}
}
Class :
import '../constants/constants.dart';
import '../models/models.dart';
import './network-call/base-service.dart';
class TokenService extends BaseService {
Future<String> getToken() async {
final dynamic response = await serviceCall(
url: ApiName().apiName(api: ServiceName.TOKEN),
method: ApiMethod.POST,
queryParameters: {
'id': Preferences().env['id'],
'secret': Preferences().env['secret'],
'type': 'rrrr'
});
Need to set this responce Data in Providers
}
}
Thank you.
try to add "notifyListeners();"
dynamic setAccessToken(data) async {
_accessToken = data;
notifyListeners();
}
I use to pass the context when needed, as dlohani suggests in the comment at the question, but I found myself in the same situation and applied a solution inspired by the communication pattern used between isolates: messages exchange.
The Provider class need to have a ReceiverPort field which listens to request messages. Ones the message reaches this listener you're inside the Provider, so you can retrieve data and send them back, again in the ReceiverPort's fashion, that is using the sendPort of the ReceiverPort registered in the requesting class.
In code below I suppose messages are Maps to clarify the type of data exchanged:
class SomeProvider with ChangeNotifier {
var _innerData = yourData;
var _providerReceiverPort = ReceiverPort();
SomeProvider() {
// The registration is necessary to "publish" the receiver port
IsolateNameServer.registerPortWithName(
_providerReceiverPort, "SomeProviderPort");
_providerReceiverPort.listen(
(message) {
// I retrieve the port to send the response to
var port = message["sendPort"];
// The answer follows the rules of messaging: maps and lists are ok
port.send({"data": _innerData.getSomething()});
}
);
}
}
class SomeClient {
var _clientReceiverPort = ReceiverPort();
someFunction(){
// First step: prepare the receiver to obtain data
_clientReceiverPort.listen(
(message) {
// Data are stored in the map containing a "data" key
var response = message["data"];
...
}
);
// Now I can retrieve the provider port by using the same name it uses to publish it
var providerPort = IsolateNameServer.lookupPortByName("SomeProviderPort");
// The message must include the sendPort to permit the provider to address the response
providerPort.send({"sendPort": _clientReceiverPort.sendPort});
}
}
The drawback of this solution is that the Provider doesn't work as a provider for the SomeClient class. You can obviously notify if any change in the listener is important for the subscribers: for example, I use this pattern to update data in the provider from a background isolate.
As I said, this is a workaround, any suggestion to improve is welcome.

How to handle two calls and the loading controller in Ionic 4

I have a requirement where I have 2 API calls, and I want the first two calls to be there for the first request. And 2nd API call to be there when navigated back.
I am calling 1st API in ngOnInit webhook and 2nd API on ionViewWillEnter webhook.
The issue which I am facing is sometimes my loader doesn’t get dismissed when both of the request complete at the same time.
So the possible solution which I am thinking is that if I could call both APIs on the first load synchronously and thereafter call another API every time the back button is clicked.
NOTE: I am using loaders in my interceptor.
CODE: For interceptor
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Clone the request to add the new header.
const authReq = req.clone();
this.showLoading();
// send the newly created request
return next.handle(authReq).pipe(catchError((error) => {
if (error.status === 401) {
this._navCtrl.navigateForward('/login');
}
return throwError(error);
}), finalize( () => {
console.log('hi');
this.dismissLoading();
})
);
}
EDIT:
Code to show loader and hide loader:
async showLoading() {
return await this._loadingCtrl.create().then(a => {
a.present();
});
}
async dismissLoading() {
return await this._loadingCtrl.dismiss();
}
In my case, I will create a LoaderService to handle the Loading by myself. The special thing is I will create a flag called isShowing, so if the loading is already showing, we just need to update the loading message by calling presentLoader function again. There will be only one Loading popup show up on your screen.
In your case, I would not recommend to display the Loader in HTTP Interceptor because we cannot handle the HTTP call stack there. Just create a new function that combines all necessary API calls and show/dismiss popup when you start/finish processing the data.
import { LoadingController } from '#ionic/angular';
import { Injectable } from '#angular/core';
#Injectable()
export class LoaderService {
private loading: HTMLIonLoadingElement;
private isShowing = false;
constructor(private loadingController: LoadingController) {}
public async presentLoader(message: string): Promise<void> {
if (!this.isShowing) {
this.loading = await this.loadingController.create({
message: message
});
this.isShowing = true;
return await this.loading.present();
} else {
// If loader is showing, only change text, won't create a new loader.
this.isShowing = true;
this.loading.message = message;
}
}
public async dismissLoader(): Promise<void> {
if (this.loading && this.isShowing) {
this.isShowing = false;
await this.loading.dismiss();
}
}
}
The simple solution would be to make a function call whenever you click the bak button and inside the function you can make a API call.
Instead of linking to the back button you can use ionViewWillEnter, which is called whenever you are about to leave a page but the downside would be it is called every time view is changed regardless of the fact that only when back button is clicked.
Also you should check, is your service singleton and it creates a single instance of ionic-loader. I think in your case more than one instance of loader is being created.
Also instead of calling the loader in interceptor, you can call showLoading() in ionViewWillEnter and hideLoading() in ionViewDidEnter() of your page.
You can create a Singleton Loader Service as shown below.
This service will take care of creating only a single instance of ionic loader.
import { Injectable } from '#angular/core';
import { LoadingController } from '#ionic/angular';
#Injectable({
providedIn: 'root'
})
export class LoaderService {
private loader: HTMLIonLoadingElement;
constructor(private loadingController: LoadingController) {}
async showLoader() {
if (!this.loader) {
this.loader = await this.loadingController.create({ message: 'Loading' });
}
await this.loader.present();
}
async hideLoader() {
if (this.loader) {
await this.loader.dismiss();
this.loader = null;
}
}
}
private loading: HTMLIonLoadingElement;
constructor(public loadingController: LoadingController) { }
public async show(): Promise<void> {
return await this.loadingController.create({
message: 'Please wait...',
spinner: 'crescent'
}).then(a => {
a.present().then(() => {
console.log('presented');
});
});
}
return await this.loadingController.dismiss().then(() =>
console.log('dismissed'));
}`enter code here`

How to integrate hockey App with Hybrid mobile app

I am trying to integrate my Hybrid Mobile App (Inonic + Cordova) with hockey App
but the problem is Hockey App is support Native apps (According to my info). So is there any guide available for that?
Hybrid App integration with Hockey app.
When I try to follow hockey app integration with android platform (hybrid app) it also said me to add some code in main activity so where i can find this
Main activity is inside Android platform... cordova/platforms/android/src/...
Put in onCreate method the Register...
There also some plugins for help in this task like https://github.com/peutetre/cordova-plugin-hockeyapp
Take into account that a lot of crash JavaScript problems do not crash in native world it would be helpful to use additional way to communicate controlled errors for example the saveException method, try to expose this by plugin into javascript, it will let store context information error: http://hockeyapp.net/help/sdk/android/3.0.1/net/hockeyapp/android/ExceptionHandler.html
I have tested the solution for Android only in a fork of the previous mentioned plugin:
https://github.com/m-alcu/cordova-plugin-hockeyapp
There are several actions available but yo only need to use "start" and "saveException" for controlled errors to be send to hockeyapps.
hockeyapp.js:
var exec = require('cordova/exec');
var hockeyapp = {
start:function(success, failure, token) {
exec(success, failure, "HockeyApp", "start", [ token ]);
},
feedback:function(success, failure) {
exec(success, failure, "HockeyApp", "feedback", []);
},
saveException:function(success, failure, description) {
exec(success, failure, "HockeyApp", "saveException", [ description ]);
}
};
module.exports = hockeyapp;
hockeyapp.java:
package com.zengularity.cordova.hockeyapp;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.json.JSONArray;
import android.widget.Toast;
import static net.hockeyapp.android.ExceptionHandler.saveException;
import net.hockeyapp.android.FeedbackManager;
import net.hockeyapp.android.CrashManager;
import net.hockeyapp.android.CrashManagerListener;
public class HockeyApp extends CordovaPlugin {
public static boolean initialized = false;
public static String token;
public static String description;
#Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {
if (action.equals("start")) {
token = args.optString(0);
CrashManager.register(cordova.getActivity(), token, null);
initialized = true;
callbackContext.success();
return true;
} else if(action.equals("feedback")) {
token = args.optString(0);
FeedbackManager.register(cordova.getActivity(), token, null);
cordova.getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
FeedbackManager.showFeedbackActivity(cordova.getActivity());
}
});
callbackContext.success();
return true;
} else if(action.equals("saveException")) {
description = args.optString(0);
if(initialized) {
Toast toast = Toast.makeText(cordova.getActivity(), "problem", Toast.LENGTH_SHORT);
toast.show();
cordova.getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
Exception e = new Exception("Send problem");
saveException(e, new CrashManagerListener() {
public String getDescription() {
return description;
}
});
}
});
callbackContext.success();
return true;
} else {
callbackContext.error("cordova hockeyapp plugin not initialized, call start() first");
return false;
}
}
else {
return false;
}
}
}
example of use this plugin in a hellowold example (index.js):
var app = {
// Application Constructor
initialize: function() {
this.bindEvents();
},
// Bind Event Listeners
//
// Bind any events that are required on startup. Common events are:
// 'load', 'deviceready', 'offline', and 'online'.
bindEvents: function() {
document.addEventListener('deviceready', this.onDeviceReady, false);
},
// deviceready Event Handler
//
// The scope of 'this' is the event. In order to call the 'receivedEvent'
// function, we must explicitly call 'app.receivedEvent(...);'
onDeviceReady: function() {
app.receivedEvent('deviceready');
},
// Update DOM on a Received Event
receivedEvent: function(id) {
var parentElement = document.getElementById(id);
var listeningElement = parentElement.querySelector('.listening');
var receivedElement = parentElement.querySelector('.received');
listeningElement.setAttribute('style', 'display:none;');
receivedElement.setAttribute('style', 'display:block;');
console.log('Received Event: ' + id);
hockeyapp.start(
function() { alert('hockeyapp initialised'); },
function(msg) { alert(msg); },
'< your APP ID >');
hockeyapp.saveException(
function() { alert('hockeyapp saveException'); },
function(msg) { alert(msg); },
'Something wrong has happened: bla bla bla...');
}
};
app.initialize();
Hockey stores these controlled exceptions in the file directory of the app and asks to send it the next time user opens app: