Kotlin Mutliplatform : Maping Swift/Objc code to Kotlin in iOSMain module? - android

I am learning KMM. I am now designing a common Location fetching in iOSMain and Android main
My problem is , I don't know to map Swift to Kotlin in iOSMain
For example,
The Swift code for , getting location is
var locationManager = CLLocationManager()
locationManager.requestWhenInUseAuthorization()
var currentLoc: CLLocation!
if(CLLocationManager.authorizationStatus() == .authorizedWhenInUse ||
CLLocationManager.authorizationStatus() == .authorizedAlways) {
currentLoc = locationManager.location
print(currentLoc.coordinate.latitude)
print(currentLoc.coordinate.longitude)
}
Kotlin side implementation:
In the above code:
How to use the Swift's .authorizedWhenInUse and .authorizedAlways in Kotlin Code ?
And the in currentLoc.coordinate.longitude , the longitude and latitude is not resolving . Why ?
Please help me

Mapping Swift (Objective-C) to Kotlin
How to use the Swift's .authorizedWhenInUse and .authorizedAlways in Kotlin Code ?
According to Kotlin's documentation on interoperability, Kotlin/Native provides bidirectional interoperability with Objective-C, not Swift, so my first recommendation would be to reference Apple's Objective-C documentation over the Swift documentation.
If you pull up the Swift documentation for .authorizedWhenInUse, you'll see you can switch the language to Objective-C:
Switch this to the Objective-C documentation to see how to reference this in Objective-C:
Given this, you should be able to use kCLAuthorizationStatusAuthorizedWhenInUse in your Kotlin code.
Referencing iOS Frameworks
Since you already have some reference code, you could also simply Command+Click (or Command+B) one of the objects (for example, CLLocationManager) which should open up the compiled Kotlin code.
Manually you can also access all iOS frameworks from the "Project" View of Android Studio → "External Libraries" and then search for the iOS framework that you are searching for.
Here, you can dig through the frameworks to find what you're looking for. Not knowing the equivalent Objective-C API, you could just search for "authorizedWhenInUse" and can find it:
Dealing with C-structs
currentLoc.coordinate.longitude , the longitude and latitude is not resolving
This is more complicated...
The location property is of type CLLocationCoordinate2D and (the important part!) is that it is contained within a CValue:
#kotlinx.cinterop.ExternalObjCClass public open class CLLocation : platform.darwin.NSObject, platform.Foundation.NSCopyingProtocol, platform.Foundation.NSSecureCodingProtocol {
...
public final val coordinate: kotlinx.cinterop.CValue<platform.CoreLocation.CLLocationCoordinate2D> /* compiled code */
Note that in Objective-C, CLLocationCoordinate2D is a C struct:
typedef struct CLLocationCoordinate2D {
...
} CLLocationCoordinate2D;
The Kotlin documentation here is thin, but it shows the methods available for CValue includes the .useContents() method.
Therefore, your code could be written as follows (compiled and confirmed that this runs and generates a location on a physical device):
val locationManager = CLLocationManager()
val currentLoc: CLLocation?
if (locationManager.authorizationStatus == kCLAuthorizationStatusAuthorizedWhenInUse ||
locationManager.authorizationStatus == kCLAuthorizationStatusAuthorizedAlways) {
currentLoc = locationManager.location
currentLoc?.coordinate?.useContents {
println("latitude = ${latitude}")
println("longitude = ${longitude}")
}
}
[Update September 2022]
If you want to dig deeper, I also published a blog post on writing iOS-platform-dependent code using Kotlin with KMM's expect/actual: artandscienceofcoding.com/science/avoid-this-kmm-technique

Related

Android Kotlin ARCore GeoSpatial API Type Mismatch Error: Earth? but Earth is Expected

I'm trying to build a Geospatial AR app using Android ARCore and Maps for Android SDK.
However, when I try to run the app, it gives the error, "type mismatch: inferred type is Earth? but Earth was expected."
This error only occurs in the code line, "activity.view.updateStatusText(earth, earth.cameraGeospatialPose)" inside the HelloGeoRenderer.kt file.
However, when I comment out the code line above, the code works fine, the map appears below the app screen like is expected, but the AR anchor does not appear.
Can anyone help me spot the issue?
Thanks in advance!
// Obtain Geospatial information and display it on the map.
val earth = session.earth
if (earth?.trackingState == TrackingState.TRACKING) {
// The Earth object may be used here.
val cameraGeospatialPose = earth.cameraGeospatialPose
activity.view.mapView?.updateMapPosition(
latitude = cameraGeospatialPose.latitude,
longitude = cameraGeospatialPose.longitude,
heading = cameraGeospatialPose.heading
)
}
activity.view.updateStatusText(earth, earth.cameraGeospatialPose)
Before getting var earth, it is necessary to configure the session to use GeospatialMode.ENABLED. Since it is possible to return null when Geospatial isn't enabled, the return type is a nullable Earth? instead of Earth, which may cause the type mismatch error.
To configure the session, you may write:
session.configure(
config.apply {
geospatialMode = Config.GeospatialMode.ENABLED
}
)
Before setting such a configuration, it is important to check if the device is compatible with Geospatial API with the function:
if (session.isGeospatialModeSupported(Config.GeospatialMode.ENABLED)) {...}

Firebase not-equal queries not working on Android

I read the latest Firebase Blog which reports firebase now supports not-equal queries. However, it is not working on this android code snippet and I am getting Unresolved reference: where error when using where.
private fun getAllContacts() {
val currentUserPhoneCredential = auth.currentUser?.phoneNumber!!
firestore.collection("Contacts")
//getting unresolved reference when using where
.where("phoneNumber", "!=", currentUserPhoneCredential)
.get().addOnSuccessListener { querySnapshot ->
for (doc in querySnapshot) {
val user = doc.toObject(User::class.java)
contactsList.add(user)
contactsNames.add(user.name!!)
}
adapter.notifyDataSetChanged()
}
}
I am using the latest Firebase Android Library and running Android SDK 30.0 on Android Studio 4.0.2
implementation 'com.google.firebase:firebase-firestore:21.7.1'
Here is an image of my code
You are using the API for JavaScript, not Java. Be sure to switch to the "java" tab when viewing the documentation, and you will see that the API is whereNotEqualTo():
.whereNotEqualTo("phoneNumber", currentUserPhoneCredential)
I think you are looking for this,
https://firebase.google.com/docs/firestore/query-data/queries#not_equal
firestore.collection("Contacts").whereNotEqualTo("phoneNumber", currentUserPhoneCredential)
You are using the API of web.
Note: For iOS, Android, and Java, the comparison operator is explicitly named in the method.
citiesRef.whereEqualTo("state", "CA")
citiesRef.whereLessThan("population", 100000)
citiesRef.whereGreaterThanOrEqualTo("name", "San Francisco")
citiesRef.whereNotEqualTo("capital", false)
So for android java and android kotlin use following code
firestore.collection("Contacts").whereNotEqualTo("phoneNumber", currentUserPhoneCredential)
for more details you can refer this link

How do I serialize an NLP classification PyTorch model

I am attempting to use a new NLP model within the PyTorch android demo app Demo App Git however I am struggling to serialize the model so that it works with Android.
The demonstration given by PyTorch is as follows for a Resnet model:
model = torchvision.models.resnet18(pretrained=True)
model.eval()
example = torch.rand(1, 3, 224, 224)
traced_script_module = torch.jit.trace(model, example)
traced_script_module.save("app/src/main/assets/model.pt")
However I am not sure what to use for the 'example' input with my NLP model.
The model that I am using from a fastai tutorial and the python is linked here: model
Here is the Python used to create my model (using the Fastai library). It is the same as in the model link above, but in a simplified form.
from fastai.text import *
path = untar_data('http://files.fast.ai/data/examples/imdb_sample')
path.ls()
#: [PosixPath('/storage/imdb_sample/texts.csv')]
data_lm = TextDataBunch.from_csv(path, 'texts.csv')
data = (TextList.from_csv(path, 'texts.csv', cols='text')
.split_from_df(col=2)
.label_from_df(cols=0)
.databunch())
bs=48
path = untar_data('https://s3.amazonaws.com/fast-ai-nlp/imdb')
data_lm = (TextList.from_folder(path)
.filter_by_folder(include=['train', 'test', 'unsup'])
.split_by_rand_pct(0.1)
.label_for_lm()
.databunch(bs=bs))
learn = language_model_learner(data_lm, AWD_LSTM, drop_mult=0.3)
learn.fit_one_cycle(1, 1e-2, moms=(0.8,0.7))
learn.unfreeze()
learn.fit_one_cycle(10, 1e-3, moms=(0.8,0.7))
learn.save_encoder('fine_tuned_enc')
path = untar_data('https://s3.amazonaws.com/fast-ai-nlp/imdb')
data_clas = (TextList.from_folder(path, vocab=data_lm.vocab)
.split_by_folder(valid='test')
.label_from_folder(classes=['neg', 'pos'])
.databunch(bs=bs))
learn = text_classifier_learner(data_clas, AWD_LSTM, drop_mult=0.5)
learn.load_encoder('fine_tuned_enc')
learn.fit_one_cycle(1, 2e-2, moms=(0.8,0.7))
learn.freeze_to(-2)
learn.fit_one_cycle(1, slice(1e-2/(2.6**4),1e-2), moms=(0.8,0.7))
learn.freeze_to(-3)
learn.fit_one_cycle(1, slice(5e-3/(2.6**4),5e-3), moms=(0.8,0.7))
learn.unfreeze()
learn.fit_one_cycle(2, slice(1e-3/(2.6**4),1e-3), moms=(0.8,0.7))
I worked out how to do this after a while. The issue was that the Fastai model wasn't tracing correctly no matter what shape of input I was using.
In the end, I used another text classification model and got it to work. I wrote a tutorial about how I did it, in case it can help anyone else.
NLP PyTorch Tracing Tutorial
Begin by opening a new Jupyter Python Notebook using your preferred cloud machine provider (I use Paperspace).
Next, copy and run the code in the PyTorch Text Classification tutorial. But replace the line…
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
With…
device = torch.device("cpu")
NOTE: It caused issues tracing when the device was set to CUDA so I forced it on to the CPU. (this will slow training, but inference on the mobile will run at the same speed as it is cpu anyway)
Lastly, run the code below to correctly trace the model to allow it to be run on Android:
data = DataLoader(test_dataset, batch_size=1, collate_fn=generate_batch)
for text, offsets, cls in data:
text, offsets, cls = text.to(device), offsets.to(device), cls.to(device)
example = text, offsets
traced_script_module = torch.jit.trace(model, example)
traced_script_module.save("model.pt")
In addition, if you would like a CSV copy of the vocab list for use on Android when you are making predictions, run the following code afterwards:
import pandas as pd
vocab = train_dataset.get_vocab()
df = pd.DataFrame.from_dict(vocab.stoi, orient='index', columns=['token'])
df[:30]
df.to_csv('out.csv')
This model should work fine on Android using the PyTorch API.

Xamarin, Android, PCL SQLLite, SQLite.NET snafu

It's pretty hard to understand the nuget packages to install in a Xamrin solution when searching around the web, there are dozens of packages, dozens of different solutions.
Right now, our solution has 2 projects, an Android one and a PCL one. Our model and data access is defined in the PCL. Our platform implementation is defined in the Android one.
We need SQLite, SQLite.Net (for the data annotations and table relations), and SQLiteExtentions for the *withchildren methods.
We are stuck in old versions because anytime we try to update anything, our frail magical way in which our installed packages are working together just falls apart. We do need to upgrade or find a way to add SQLCipher to this weird snafu of nuget packages.
Our current packages installed, which works:
Android project
Mono.Data.Sqlite.Portable 1.0.3.5 (not sure why...)
SQLite.Net.Core-PCL 3.1.1
SQLite.Net.Platform.XamarinAndroidN 3.1.1
SQLite.Net-PCL 3.1.1
SQLiteNetExtensions 1.3.0 (required for GetWithChildren etc.)
SQLitePCL.raw 0.9.3
PCL Project (model definition and data access methods)
Mono.Data.Sqlite.Portable 1.0.3.5 (not sure why...)
SQLite.Net.Core-PCL 3.1.1
SQLite.Net.Platform.XamarinAndroidN 3.1.1
SQLite.Net-PCL 3.1.1
SQLiteNetExtensions 1.3.0 (required for GetWithChildren etc.)
SQLitePCL.raw 0.9.3
Currently, if we update the SQLiteExtensions to 2.0, a bunch of other SQLite nuget packages are installed, breaking the frail stability of our data access code (fails on the *WithChildren methods with:
Severity Code Description Project File Line Suppression State
Error CS1929 'SQLiteConnection' does not contain a definition for
'GetWithChildren' and the best extension method overload
'ReadOperations.GetWithChildren<TEntity>(SQLiteConnection, object, bool)'
requires a receiver of type 'SQLiteConnection'
We'd also need to incorporate SQLiteCipher and no combinations of packages can be made to work with our solution.
Our Android platform specific implementation:
#region Usings
using OURPCLLib.DataAccess;
using Serilog;
using SQLite.Net;
using SQLite.Net.Interop;
using SQLite.Net.Platform.XamarinAndroid;
using System;
using System.IO;
#endregion Usings
public class AndroidSQLiteDatabase : SQLiteDatabaseAccess
{
protected ISQLitePlatform SQLitePlatform
{
get { return new SQLitePlatformAndroidN(); }
}
protected override SQLiteConnection GetConnection()
{
var conn = new SQLiteConnection(
SQLitePlatform,
"dbpathforus.sqlite",
SQLiteOpenFlags.ReadWrite |
SQLiteOpenFlags.FullMutex |
SQLiteOpenFlags.ProtectionCompleteUnlessOpen |
SQLiteOpenFlags.Create |
SQLiteOpenFlags.SharedCache);
return conn;
}
}
The (simplified) base data access class in the PCL:
#region Usings
using OURPCLLib.DataAccess.Entities;
using SQLite.Net;
using SQLite.Net.Attributes;
using SQLite.Net.Interop;
using SQLiteNetExtensions.Extensions;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
#endregion Usings
public abstract class SQLiteDatabaseAccess
{
protected abstract SQLiteConnection GetConnection();
// Example of one of the many methods accessing the DB using SQLite.Net
public bool Any<TEntity>(Expression<Func<TEntity, bool>> expression)
where TEntity : class, IBaseEntity, new()
{
using (var currentConnection = this.GetConnection())
{
return currentConnection.Table<TEntity>().Where(expression).FirstOrDefault() != null;
}
}
// Example of one of the methods accessing the DB using SQLiteExtentions
public TEntity GetWithChildren<TEntity>(int id, bool recursive = false)
where TEntity : class, IBaseEntity, new()
{
using (var currentConnection = this.GetConnection())
{
return currentConnection.GetWithChildren<TEntity>(id, recursive);
}
}
}
Anyone can help us as to how to use SQLite with SQLIte.net, SQLiteExtentions and SQLIte cipher on a project like ours? (data access in a pcl and connection implementation in an android project?
For anyone wondering, I solved it, with minimal code impact, and some loss of functionality but not much, by only installing SQLiteNetExtensions and letting it get its SQLite dependency by itself.

Titanium Hyperloop access to android.os.SystemProperties

I have been trying a ( i hope) simple bit of Android hyperloop code directly within a titanium project (using SDK 7.0.1.GA and hyperloop 3).
var sysProp = require('android.os.SystemProperties');
var serialNumber = sysProp.get("sys.serialnumber", "none");
But when the app is run it reports
Requested module not found:android.os.SystemProperties
I think this maybe due to the fact that when compiling the app (using the cli) it reports
hyperloop:generateSources: Skipping Hyperloop wrapper generation, no usage found ...
I have similar code in a jar and if I use this then it does work, so I am wondering why the hyperloop generation is not being triggered, as I assume that is the issue.
Sorry should have explained better.
This is the jar source that I use, the extraction of the serial number was just an example (I need access to other info manufacturer specific data as well), I wanted to see if I could replicate the JAR functionality using just hyperloop rather that including the JAR file. Guess if it's not broke don't fix it, but was curious to see if it could be done.
So with the feedback from #miga and a bit of trial and error, I have come up with a solution that works really well and will do the method reflection that is required. My new Hyperloop function is
function getData(data){
var result = false;
var Class = require("java.lang.Class");
var String = require("java.lang.String");
var c = Class.forName("android.os.SystemProperties");
var get = c.getMethod("get", String.class, String.class);
result = get.invoke(c, data, "Error");
return result;
}
Where data is a string of the system property I want.
I am using it to extract and match a serial number from a Samsung device that is a System Property call "ril.serialnumber" or "sys.serialnumber". Now I can use the above function to do what I was using the JAR file for. Just thought I'd share in case anyone else needed something similar.
It is because android.os.SystemProperties is not class you can import. Check the android documentation at https://developer.android.com/reference/android/os/package-summary.html
You could use
var build = require('android.os.Build');
console.log(build.SERIAL);
to access the serial number.

Categories

Resources