Loading Android Contacts with LoaderManager.LoaderCallbacks in a Promise - android

I'm writing an angular 2 service for my NativeScript app that retrieves all the user's contacts. My current implementation uses a synchronous getContentResolver method like the one used in nativescript-contacts. The cursor.getCount reports over 7k cursors and freezes the app for a total of ~3 seconds. No good.
I'm following this guide, using-loaders-in-android, and having no luck so far.
Here's what I have so far:
declare let android: any
declare let java: any
import * as application from "application"
import {Injectable} from "#angular/core"
#Injectable()
export class ContactsService
extends android.support.v4.app.LoaderManager
implements android.support.v4.app.LoaderManager.LoaderCallbacks {
private contactsLoader: any = new android.support.v4.app.LoaderManager.LoaderCallbacks(
class extends android.support.v4.app.LoaderManager.LoaderCallbacks {
onCreateLoader(id, args) {
let projection: Array<string> = [
android.provider.ContactsContract.RawContactsColumns.CONTACT_ID,
android.provider.ContactsContract.ContactsColumns.DISPLAY_NAME,
android.provider.ContactsContract.ContactsColumns.HAS_PHONE_NUMBER,
android.provider.ContactsContract.ContactsColumns.LOOKUP_KEY,
android.provider.ContactsContract.ContactsColumns.PHOTO_URI,
android.provider.ContactsContract.CommonDataKinds.Phone.NUMBER,
android.provider.ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER,
android.provider.ContactsContract.CommonDataKinds.Phone.TYPE,
android.provider.ContactsContract.DataColumns.MIMETYPE,
]
return new android.support.v4.content.CursorLoader(
application.android.foregroundActivity,
android.provider.ContactsContract.Data.CONTENT_URI,
'data2 IS 2',
null,
null
).loadInBackground()
}
onLoadFinished(param0: android.support.v4.content.Loader, param1: java.lang.Object): void {
global.tnsconsole.dump('onLoadFinished > param0', param0)
global.tnsconsole.dump('onLoadFinished > param1', param1)
}
onLoaderReset(param0: android.support.v4.content.Loader): void {
global.tnsconsole.dump('onLoaderReset > param0', param0)
}
}
)
getContactsAsync(): Promise<any> {
// since the initLoader method is part of android.support.v4.app.LoaderManager
this.initLoader(1, null, this)
// but i get this error :(
// EXCEPTION: Error: Cannot convert object to Landroid/support/v4/app/LoaderManager$LoaderCallbacks; at index 2
return Promise.resolve([])
}
}
Does anyone know how to properly implement LoaderManager.LoaderCallbacks so I can run the cursor in a background thread?
Thank you!

Related

Usb List Devices in Xamarin.Forms

I try to do list of usb devices, connected by serial odt with smartphone, within xamarin.forms.
To do that I use this project https://github.com/anotherlab/UsbSerialForAndroid
How to do listview in shared project with devices from Project.Droid.MainActivity? I tried to do that with dependency service:
This is my Page1(where I want to have listview):
public partial class Page1 : ContentPage {
public Page1()
{
InitializeComponent();
DependencyService.Get<Interface1>().moj();
}
}
My interface:
namespace SensDxMobileApp.Views.MainWindow {
public interface Interface1 {
void moj();
}
}
And MyActivity(Droid project):
[assembly: Xamarin.Forms.Dependency(typeofProject.Droid.MainActivity))]
namespace Project.Droid {
public class MainActivity: Interface
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
listView = new Android.Widget.ListView;
}
public async void moj()
{
adapter = new UsbSerialPortAdapter(this);
listview.Adapter = adapter;
listView.ItemClick += async (sender, e) =>
{
await OnItemClick(sender, e);
};
await PopulateListAsync();
detachedReceiver = new UsbDeviceDetachedReceiver(this);
RegisterReceiver(detachedReceiver, new IntentFilter(UsbManager.ActionUsbDeviceDetached));
}
}
But I have an error "Object reference not set to an instance of an object.", on " DependencyService.Get().moj()" in Page1();
Did someone do something similar? Thanks
If you going the DependencyService route, then you'll want to create a separate class that implements Interface1 and registering that as a dependency service. I don't think you can register the MainActivity as a DependencyService implementation.
One problem that you are going to hit this is mostly async code and callbacks.
You also shouldn't be newing up an Android ListView as a DependencyService call. That would be better suited as a custom renderer. As a DependencyService implementation, you would want the moj() method to return data that can be consumed by the Xamarin.Forms code. You would need more than just that method. You would need code to initialize the UsbSerialPort class, code to query the list of devices, and then invoke a callback that sends back that list, In theory anyway. I never tested that library with Forms.

How to access services and data from an android background service in a NativeScript-Angular App?

I have a NativeScript-Angular App that get data from an API. To collect the data there are diffrent (angular) services with diffrent (api) endpoints. One type of data are very important therefor the app have to look for new data in a short interval (pulling), even the app is in background. The right choice for this task seems to be the android background services.
After a time of searching for a beast practice or examples, i found the nativescript-geolocation plugin with its demo + background-service.ts and the issue #157 where an angular-example attached. In this two examples and a few others the main action is a console.log(...), nice for the first try but not for a real app.
I want to use the existing service to fetch and handle the data from the API. I tried the method from Bass How to use (angular) HTTP Client in native background service - NativeScript with not full success. The injector gives me instances from my services but new once and not the instances from the main task. This prevent me to access all local app data with was initialized by the app at start time.
Following my example to get the android background service running and output a counter. For testing i call the length() method from my ExampleService which return in this case always 0 because the array is empty in this instance from ExampleService. In the main instance from ExampleService the array has entries.
AndroidMainifest.xml - define the android service
<service android:name="com.tns.ExampleBackgroundService"
android:exported="false">
</service>
ExampleBackgroundService.ts
import { Injectable, Injector } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { DatabaseService } from '~/services/database.service';
import { MessageService } from '~/services/message.service';
#JavaProxy('com.tns.ExampleBackgroundService')
// #ts-ignore TS2304
export class ExampleBackgroundService extends (<any>android).app.Service {
protected injector: Injector;
protected exampleService: ExampleService;
protected id: number;
constructor() {
super();
return global.__native(this);
}
onCreate() {
this.injector = Injector.create({
providers:
[
{provide: HttpClient, useClass: HttpClient, deps: []},
{provide: DatabaseService, useClass: DatabaseService, deps: []},
{provide: MessageService, useClass: MessageService, deps: []},
{
provide: ExampleService,
useClass: ExampleService,
deps: [HttpClient, DatabaseService, MessageService]
},
]
});
}
onStartCommand(intent, flags, startId) {
this.super.onStartCommand(intent, flags, startId);
this.exampleService = this.injector.get(ExampleService);
let count = 0;
// #ts-ignore T2322
this.id = setInterval(() => {
console.log('count: ', count++, this.exampleService.length());
}, 1000
);
// #ts-ignore TS2304
return (<any>android).app.Service.START_STICKY;
}
}
start background service
const utils = require('tns-core-modules/utils/utils');
// ...
let context = utils.ad.getApplicationContext();
// #ts-ignore
let intent = new (<any>android).content.Intent(context, ExampleBackgroundService.class);
context.startService(intent);
How can i access the local data from the main task and how can i interact with the angular services from the main task?

NativeScript navigate to another page from custom Android activity

I am developing an APP with wechat login feature. After getting approval from wechat it will call a custom activity of my NativeScript APP. I am getting response correctly but how can I move to another page instated of the home page after doing some verification. I am using NativeScript + Angular.
Sample code
getJSON("https://api.weixin.qq.com/sns/oauth2/access_token?appid=ID&secret=SECRET&code=" + res.code + "&grant_type=authorization_code").then((res) => {
console.dir(res);
// ===> here I want navigation
}, err => {
console.dir(err);
})
I tried like this:
frame.topmost().navigate("src/app/login/login.component");
but getting error:
JS: Unhandled Promise rejection: Cannot set property '_moduleName' of undefined ; Zone: ; Task: Promise.then ; Value: TypeError: Cannot set property '_moduleName' of undefined TypeError: Cannot set property '_moduleName' of undefined
Please give me some suggestions. Thanks in advance :)
Fire an event from the Activity callback, something like
import { android } from "tns-core-modules/application";
...
public onResp(res: com.tencent.mm.opensdk.modelbase.BaseResp) {
console.log("onResp");
console.dir(res);
androidApp.notify(<AndroidActivityEventData>{ eventName: 'wxapiresponse', object: android, activity: this });
}
In app component, listen to the event
export class AppComponent implements OnInit {
constructor(private ngZone: NgZone, private routerExtensions: RouterExtensions) {}
ngOnInit() {
application.android.on('wxapiresponse', this.wxApiResponse, this);
}
wxApiResponse() {
// making sure the event callback runs inside Angular zone
this.ngZone.run(() => {
this.routerExtensions.navigate(...);
});
}
}
A similar problem cost me an entire Weekend, without knowing how the companion controller .js or .ts files look like I can only recommend you go to the basics / source:
https://docs.nativescript.org/core-concepts/navigation#frame
it helped me find the problem in my code.
If you wrote a custom Page that does not start from a template chances are you wrote the controller too and it's easy to overlook some line that is present in the samples - even thou it looks like you don't need it.
If you paste your controller JS we could understand your context.
For instance, here is piece of code you should definitely include to help you debug:
in your page-you-navigate-to.xml
<Page loaded="onPageLoaded" class="page">
...
</Page>
in your page-you-navigate-to.ts
import { EventData } from "tns-core-modules/data/observable";
import { Page } from "tns-core-modules/ui/page";
export function onPageLoaded(args: EventData): void {
const page = <Page>args.object;
var context = page.navigationContext;
page.bindingContext = context;
console.log("Target page Loaded.");
}
Start your debugger, and step through see where you have null values and read up on those method calls and their parameters.

Unable to use MatrixCursor.newRow() in Robolectric

I have a ContentProvider that is attempting to call matrixCursor.newRow(). This is crashing with a NullPointerException in private method MatrixCursor.ensureCapacity(). Debugging into this, I see that matrixCursor.data is null (appears the ShadowMatrixCursor does not instantiate it in the constructor).
I'm using the latest Robolectric jar, version 2.2.
#RunWith(RobolectricTestRunner.class)
public class MatrixCursorTest {
#Test
public void thisCrashesInNewRow() {
MatrixCursor c = new MatrixCursor(new String[] { "test", "cols" }, 1);
MatrixCursor.RowBuilder b = c.newRow(); // This crashes with NPE
}
}
I'm trying to understand how I can get around this. I've tried creating "MyShadowMatrixCursor" as follows, but I just don't see how I can override the behavior of newRow() to just simply return an empty RowBuilder (whose constructor is default/package-private, so not accessible to my Shadow.)
import android.database.MatrixCursor;
import android.database.MatrixCursor.RowBuilder;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
#Implements(value = MatrixCursor.class, inheritImplementationMethods = true)
public class MyShadowMatrixCursor extends org.robolectric.shadows.ShadowMatrixCursor {
#RealObject
private MatrixCursor cursor;
#Implementation
public RowBuilder newRow() {
// this causes infinite loop because shadow intercepts it again:
return cursor.newRow();
// doesn't work because RowBuilder constructor is package-private
...return cursor.new RowBuilder()
// how can i return an instance of MatrixCursor.RowBuilder instead?
}
#Implementation
public void ensureCapacity(int i) {
// Override the private ensureCapacity
// do nothing
}
}
So, my questions from code above:
How can I return an instance of the MatrixCursor.RowBuilder?
Is it possible to shadow a private method as I'm attempting with
ensureCapacity() above? EDIT: YES, just make it "public" in the shadow class.
Pretty new to Robolectric, so hopefully something I'm just overlooking?
EDIT: Figured out how to override a private method by just making it public in the shadow class. However, now I'm just getting NPEs elsewhere as the state of the MatrixCursor just doesn't appear to be set up at all?
Erich suggested I try Robolectric 2.3-Snapshot build, so I did and it does indeed fix this issue. See https://groups.google.com/forum/#!topic/robolectric/I5z5N5NH4Pw.
Unrelated, but two new issues are occurring now with ContentResolver.getType() which I submitted a patch here: https://github.com/robolectric/robolectric/pull/954 and am still working on ContentProvider.openFile().

Android - Return value get in a custom listener

Please excuse my french english !
So, I have got a problem in my Android code...
I call a method which much return a arrayList of a custom object... but this arrayList is loaded asynchronously and returned in a listener.
Do you know how I could return datas when the method of my listener is called ?
Here my code :
public static ArrayList<Advert> getAdverts(Context context) {
// Initialize
RestHelper restHelper = new RestHelper();
// Set the listener
restHelper.setOnRestListener(new OnRestListener<Advert>() {
#Override
public void onDataAvailable(ArrayList<Advert> result) {
// -- Datas are loaded : now we must return them ! --
}
});
// Launch the async query
restHelper.getRemoteAdverts();
}
Thanks !!
I'm not sure about the problem. You have an async method that you can call, but it can't return your result since its an async method, so you have 2 solutions :
- Wait in the method that all data are available, but you loose the async benefits.
- Implements an Oberserver/Observable pattern.
For the first option, look into the RestHelper, maybe you have already the solution.
Example for the second option :
public static void requestAdverts(Context context) {
RestHelper restHelper = new RestHelper();
final AdvertListener thisInstance = this;
restHelper.setOnRestListener(new OnRestListener<Advert>() {
#Override
public void onDataAvailable(ArrayList<Advert> result) {
thisInstance.notifyDataLoaded(result);
}
});
// Launch the async query
restHelper.getRemoteAdverts();
Create an Interface :
interface AdvertListener {
notifyDataLoaded(ArrayList<Advert> result);
}
And finally let the main class (the one that call the requestAdverts method) implements your new interface.
Well, please revisit your function design, you are tiring to make asynchronously load inside a synchronous function (getAdverts). If your function is synchronous, then just synchronously load the list and return.
If for any reason, if you want to go ahead with current approach, please block the caller after setOnRestListener and when you get a callback(onDataAvailable) unlock it and return your list.
You can use ConditionVariable, for this.
Block the caller:
ConditionVariable.block ();
ConditionVariable.close ();
UnBlock/open the caller:
ConditionVariable.open ();
Hope, this helps.

Categories

Resources