I am working on a SendBird chat integration with Rubymotion. I am running into some problems since I am unable to get a Messaging Channel List from the SDK due to my limited knowledge of Objective-C / Swift structures.
I would appreciate some help translating the following code into RubyMotion code:
Objective-C:
- (void) queryMessagingChannels
{
messagingChannelListQuery = [SendBird queryMessagingChannelList];
[messagingChannelListQuery setLimit:15];
[messagingChannelListQuery nextWithResultBlock:^(NSMutableArray *queryResult) {
...
for (int i = 0; i < [queryResult count]; i++) {
SendBirdMessagingChannel *mc = (SendBirdMessagingChannel *)[queryResult objectAtIndex:i];
...
}
...
} endBlock:^(NSError *error) {
}];
}
Swift:
func queryMessagingChannels() {
messagingChannelListQuery = SendBird.queryMessagingChannelList()
messagingChannelListQuery?.setLimit(15)
messagingChannelListQuery?.nextWithResultBlock({ (queryResult) -> Void in
....
for model in queryResult {
let mc: SendBirdMessagingChannel = model as! SendBirdMessagingChannel
....
}
....
}, endBlock: { (code) -> Void in
})
}
So far I have this, but it is failing with an error.
Rubymotion:
mp "Iterating over MessagingChannelList"
#messagingChannelListQuery = SendBird.queryMessagingChannelList()
#messagingChannelListQuery.setLimit(5)
#messagingChannelListQuery.nextWithResultBlock.each do |queryResult|
#messaging_channels << queryResult
end
# reload tableview data here
Error:
chat_messaging_screen.rb:49:in `load_async': undefined method `nextWithResultBlock' for #<SendBirdMessagingChannelListQuery:0x117c672e0> (NoMethodError)
I really don't know how to properly set up the block in this case.
Ok. So the nextWithResultBlock actually requires passing endBlock too, so this finally solved it:
#messagingChannelListQuery.nextWithResultBlock(
-> (queryresult) {
#do something here with queryresult.each
},
endBlock: -> (code) {
}
)
The NoMethodError was throwing me off.
Related
Currently, this is how I read from C++ using Flutter:
final Uint8List result = await platform.invokeMethod(Common.MESSAGE_METHOD, {"message": buffer});
It is handled by Kotlin like this:
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == MESSAGE_METHOD) {
val message: ByteArray? = call.argument<ByteArray>("message")
//... //response = Read something FROM C++
result.success(response)
Since this happens in the main thread, if I take too much time to answer, I make Flutter's UI slow.
Is there a solution to get C++ data in an async way?
I know that Flutter has support for event channels to send data back from C++ to Flutter. But what about just requesting the data on the Flutter side and waiting for it to arrive in a Future, so I can have lots of widgets inside a FutureBuilder that resolves to something when ready?
If reading something from C++ is a heavy process, You can use AsysncTask to perform it in the background for android.
internal class HeavyMsgReader(var result: MethodChannel.Result) : AsyncTask<ByteArray?, Void?, String?>() {
override fun doInBackground(vararg message: ByteArray?): String {
//... //response = Read something FROM C++
return "response"
}
override fun onPostExecute(response: String?) {
result.success(response)
}
}
Calling async task:
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == MESSAGE_METHOD) {
val message: ByteArray? = call.argument<ByteArray>("message")
HeavyMsgReader(result).execute(message);
Hopefully this will work
import 'dart:async';
Future<Uint8List> fetchData(buffer) async {
final Uint8List result = await platform.invokeMethod(Common.MESSAGE_METHOD, {"message": buffer});
return result;
}
And just call it, like this
fetchData(buffer).then((result) => {
print(result)
}).catchError(print);
Proof that its working:
import 'dart:async';
Future<String> fetchUserOrder() async {
await Future.delayed(Duration(seconds: 5));
return 'Callback!';
}
Future<void> main() async {
fetchUserOrder().then((result) => {
print(result)
}).catchError(print);
while(true){
print('main_thread_running');
await Future.delayed(Duration(seconds: 1));
}
}
output:
main_thread_running
main_thread_running
main_thread_running
main_thread_running
main_thread_running
Callback!
main_thread_running
main_thread_running
...
I do have a UIWebView included where a public URL is loaded; unfortunately, vcard and ical-Links are not handled, i.e. nothing happens when I click on them.
I tried to set all data detectors, no luck unfortunately.
In the Xcode-log, I get this here when clicking on such a link:
2017-07-14 13:43:00.982413+0200 xxx[2208:967973] WF: _userSettingsForUser mobile: {
filterBlacklist = (
);
filterWhitelist = (
);
restrictWeb = 1;
useContentFilter = 0;
useContentFilterOverrides = 0;
whitelistEnabled = 0;
}
In Safari, the same stuff works as expected.
If I use UIApplication.shared.openURL(icsOrVcardUrl) Safari gets opened and from there everything works as expected again, but I don't want the user to leave the app...
EDIT
This doesn't work either:
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if let url = request.url {
if url.absoluteString.contains("=vcard&") || url.absoluteString.contains("/ical/") {
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = URLRequest(url:url)
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
DispatchQueue.main.async {
self.documentController.url = tempLocalUrl
self.documentController.presentPreview(animated: true)
}
}
}
task.resume()
return false
}
}
return true
}
Use a UIDocumentInteractionController to preview without leaving your app.
I tested it quickly with an .ics file and it works fine.
Implement the UIDocumentInteractionControllerDelegate protocol
extension MainViewController: UIDocumentInteractionControllerDelegate {
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
return self;
}
}
Create an instance of the interaction controller:
let documentController = UIDocumentInteractionController()
Intercept the clicks in your UIWebView in shouldStartLoadWithRequest, return false for links you want to handle with the in-app preview and true for all the rest. And finally:
func previewDocument(_ url: URL) {
documentController.url = url
documentController.presentPreview(animated: true)
}
Here it is in the simulator
EDIT:
In response to the comment to this answer:
The reason it doesn't work for you is because the UIDocumentInteractionController depends on the file extension. The extension of the temp file is .tmp
Renaming the file after the download solves the problem. Quick and dirty example:
let task = session.downloadTask(with: url!) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
do {
let filemgr = FileManager.default
let newUrl = tempLocalUrl.appendingPathExtension("ics")
try filemgr.moveItem(at: tempLocalUrl, to: newUrl)
DispatchQueue.main.async {
self.documentController.url = newUrl
self.documentController.presentPreview(animated: true)
}
} catch let error {
print("Error!!!: \(error.localizedDescription)")
}
}
}
task.resume()
In this case it is advisable to clean after yourself, because the file won't be deleted after the task completes although the OS will delete it eventually, when space is needed. If you often access the same urls, Library/Caches/ may be a better place for this files, just come up with good naming schema, and check if the file doesn't exist already.
Kindly help with Real objects adding/updating
I'd like store and update User class.
User class consist of Client class,
Client class consist of Avatar property and Rooms List.
Issue is that I'm facing error "The Realm is already in a write transaction" because my Client class avatar property and rooms list are fetched and pushed to Realm in different closures at the same time.
func fetchRooms() {
roomsDelegate?.contactRooms(entityID: entityID,
success: {rooms in
self.addRooms(rooms: rooms)
},
fail: { error in
print (error)
})
}
func addRooms(rooms: [VMRoom]?) {
if let r = rooms {
do{
try realm?.write {
realm?.add(r, update: true)
self.rooms.append(objectsIn: r)
} }
catch let e {
print(e.localizedDescription)
}
}
}
func getAvatarURL() {
do{
try realm?.write {
avatarURL = avatarDelegate?.contactAvatarURL(eExtention: eExtention)
} }
catch let e {
print(e.localizedDescription)
}
}
Like Realm is saying, you've got an error in your app's logic if you're trying to open up two write transactions in one thread. I'd recommend you review your logic to see if you can make it more streamlined.
But in any case, to fix your current code, one way to mitigate this would be to check you're not already in a write transaction when you're setting the avatar URL.
func getAvatarURL() {
let inWriteTransaction = realm?.isInWriteTransaction
do {
if !inWriteTransaction {
realm?.beginWrite()
}
avatarURL = avatarDelegate?.contactAvatarURL(eExtention: eExtention)
if !inWriteTransaction {
try realm.commitWrite()
}
catch let e {
print(e.localizedDescription)
}
}
I am talking about loopback push component. I am trying to intercept the "create" method of "Installation" model. My code looks like this -
server/boot/installationex.js
module.exports = function (app) {
var Installation = app.models.Installation;
var create = Installation.create;
Installation.create = function (data, cb) {
//reinitializing old implementation
this.create = create;
console.log("Received data: "+JSON.stringify(data));
if (!data || !data.imei) {
console.log("No data or imei was provided, creating new");
this.create(data, cb);
return;
}
//saving 'this' reference
var that = this;
//search by imei filter
var filter = {where: {imei: data.imei}};
this.findOne(filter, function (err, result) {
if (err) {
console.log("Error occurred while looking for installation by IMEI");
cb(err);
return;
}
if (!result) {
console.log("No installation found by IMEI, will create a new installation");
that.create(data, cb);
return;
}
console.log("Found existing installation with id: " + JSON.stringify(result));
result.deviceToken = result.gpsLocation = result.osVersion = result.vendor = result.phoneNumbers = null;
if (data.deviceToken) {
result.deviceToken = data.deviceToken;
}
if (data.gpsLocation) {
result.gpsLocation = data.gpsLocation;
}
if (data.osVersion) {
result.osVersion = data.osVersion;
}
if (data.vendor) {
//result.vendor=data.vendor;
result.vendor = 'jahid';
}
if (data.phoneNumbers) {
result.phoneNumbers = data.phoneNumbers;
}
that.upsert(result, cb);
});
}
}
Unfortunately this code is invoked only once, I mean the first time. After that this code is never invoked. I became sure by looking at the log. It only prints the log first time. After that it does not print any log.
Any idea why this glue code is only invoked once? My intention is to intercept all create method invocation for Installation model. And check if there is already an entry for supplied "IMEI", if so then reuse that. Otherwise create new.
Thanks in advance.
Best regards,
Jahid
What I would start here with is:
instead of implementing your own intercepting mechanism use Model Hooks
check out findOrCreate() method
boot scripts are only run once during application startup. if you want a function that triggers every time a function is called, use a remote hook or model hook. probably something along the lines of:
...
Installation.beforeRemote('create', ...
...
see http://docs.strongloop.com/display/LB/Adding+logic+to+models for more info
I am looking at writing a firefox extension which will take a url, apply a regex to it to produce a second url.
I then need to have firefox redirect to this new URL without the user having to do anyything.
Does anyone have any examples I could use to learn how to do this. I've used the firefox tutorials to get as far as this..
// Import the page-mod API
var pageMod = require("sdk/page-mod");
// Import the self API
var self = require("sdk/self");
// Create a page mod
// It will run a script whenever a ".org" URL is loaded
// The script replaces the page contents with a message
pageMod.PageMod({
include: "*",
contentScript: 'window.alert("Matched Page");'
})
So my question is, how would I do this.
For instance, if a user types in http://www.mywebsite/data/7287232/wherever, I'd like them to be redirected to http://www.anotherwebsite/folder/7287232
Well.. answering to the initial head line:
https://addons.mozilla.org/en-US/firefox/addon/redirector
Or is that actually you? #ScaryAardvark ?
that example is very complex, the main purpose of that traceable channel example is to get a COPY of the sourcecode that gets loaded at that uri.
const { Ci, Cu, Cc, Cr } = require('chrome'); //const {interfaces: Ci, utils: Cu, classes: Cc, results: Cr } = Components;
Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/devtools/Console.jsm');
var observers = {
'http-on-modify-request': {
observe: function (aSubject, aTopic, aData) {
console.info('http-on-modify-request: aSubject = ' + aSubject + ' | aTopic = ' + aTopic + ' | aData = ' + aData);
var httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
var requestUrl = httpChannel.URI.spec
if (/\.org/.test(requestUrl) || /http\:\/\/www\.mywebsite\/data\/7287232\/.+/.test(requestUrl)) {
httpChannel.redirectTo(Services.io.newURI('http://www.anotherwebsite/folder/7287232', null, null));
}
},
reg: function () {
Services.obs.addObserver(observers['http-on-modify-request'], 'http-on-modify-request', false);
},
unreg: function () {
Services.obs.removeObserver(observers['http-on-modify-request'], 'http-on-modify-request');
}
}
};
to register the observer on startup of addon run this:
//register all observers
for (var o in observers) {
observers[o].reg();
}
and on shutdown of addon unregister all observers like this:
//unregister all observers
for (var o in observers) {
observers[o].unreg();
}