I build a capacitor plugin with permission requests. This plugin uses a native library that should actually request the permission. Therefore, the capacitor plugin implements the PermissionManagerInterface for the library.
My trouble: The PermissionCallback doesn't work within the PermissionManager class. It needs to be in MyPlugin, I think. That's not what I need. Is there a way to get the callback within the PermissionManager?
(I know, I could implement the PermissionManagerInterface within MyPlugin, but it is not very pretty to do everything in one class)
public class MyPlugin extends Plugin {
private MyPluginImpl implementation = new MyPluginImpl(this);
#PluginMethod
public void start(PluginCall call) {
implementation.start(call);
}
public void requestPermission (String alias, PluginCall call, String aliasCallback) {
this.requestPermissionForAlias(alias, call, aliasCallback);
}
}
public class MyPluginImpl {
private PermissionManager permissionManager;
private MyPlugin plugin;
private myFrameworkObject;
public DAAssistantPlugin (MyPlugin plugin) {
this.plugin = plugin;
}
public void start(PluginCall call) {
this.permissionManager = new PermissionManager(plugin, call);
this.myFrameworkObject = new MyFrameworkClass(plugin.getActivity(), this.permissionManager);
this.myFrameworkObject.start();
}
}
public class PermissionManager implements PermissionManagerInterface {
private MyPlugin plugin;
private PluginCall call;
private CompletableFuture<Boolean> microphonePermissionFuture;
public PermissionManager(MyPlugin plugin, PluginCall call) {
this.plugin = plugin;
this.call = call;
}
#Override
public Future<Boolean> requestMicrophonePermission() {
this.microphonePermissionFuture = new CompletableFuture<>();
if (this.plugin.getPermissionState("microphone") != PermissionState.GRANTED) {
this.plugin.requestPermission("microphone", this.call, "microphoneCallback");
} else {
this.microphonePermissionFuture.complete(true);
}
return this.microphonePermissionFuture;
}
#PermissionCallback
private void microphoneCallback(PluginCall call) { // not called
if (this.plugin.getPermissionState("microphone") == PermissionState.GRANTED) {
this.microphonePermissionFuture.complete(true);
} else {
this.microphonePermissionFuture.complete(false);
}
}
}
Related
I am generating a plugin in Flutter that will integrate a native library *.aar and this library needs to be initialized in the Application class of android, since it will extend the Application class of the library. The problem I am having is that at no time does it seem that the Application class is used in the Flutter plugin, I have tried to create the application class and define it in the plugin manifest, but at no time does it seem to enter the onCreate. I do not see how to solve this situation, any indication is welcome.
My class Application:
public class App extends SDKApplication{
#Override
public void onCreate() {
super.onCreate();
Log.e("App", "onCreate");
}
}
My manifest
<application
android:name=".App">
</application>
and the plugin class:
public class SdkPlugin implements FlutterPlugin, MethodCallHandler {
/// The MethodChannel that will the communication between Flutter and native Android
///
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
/// when the Flutter Engine is detached from the Activity
private MethodChannel channel;
private EMTingSDK sdk = EMTingSDK.getInstance();
private Context context;
public SdkPlugin(){
Log.e("PRUEBAS","onMethodCall");
new App();
}
#Override
public void onAttachedToEngine(#NonNull FlutterPluginBinding flutterPluginBinding) {
context = flutterPluginBinding.getApplicationContext();
channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), Constants.CHANNEL);
channel.setMethodCallHandler(this);
}
#Override
public void onMethodCall(#NonNull MethodCall call, #NonNull Result result) {
Log.e("PRUEBAS","onMethodCall");
if (call.method.equals("getPlatformVersion")) {
result.success("Android " + android.os.Build.VERSION.RELEASE);
}else {
result.notImplemented();
}
}
#Override
public void onDetachedFromEngine(#NonNull FlutterPluginBinding binding) {
channel.setMethodCallHandler(null);
}
}
P.D: I need to start it in the Application because the developers of the native library told us that it had to be spent in this way, then I will put a little more code regarding the application.
public class SDKApplication extends CompanyApplication implements ActivityLifecycleCallbacks {
public SDKApplication() {
}
public void onCreate() {
SDK.getInstance().setContext(this.getApplicationContext());
super.onCreate();
Log.i("InfoSDK", "SDKApplication - init from App ");
this.registerActivityLifecycleCallbacks(this);
}
public void onActivityCreated(Activity activity, Bundle bundle) {
SDK.getInstance().setHostActivity(activity);
}
public void onActivityStarted(Activity activity) {
}
public void onActivityResumed(Activity activity) {
SDK.getInstance().setHostActivity(activity);
}
public void onActivityPaused(Activity activity) {
}
public void onActivityStopped(Activity activity) {
}
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
}
public void onActivityDestroyed(Activity activity) {
}
}
And the Company Application is:
public class CompanyApplication extends Application {
public CompanyApplication () {
}
public void onCreate() {
super.onCreate();
RequestQueue mainQueue = null;
if (SDK.getInstance().isDevelopEnviroment()) {
mainQueue = Volley.newRequestQueue(this.getApplicationContext(), new HurlStack((UrlRewriter)null, this.newSslSocketFactory()));
} else {
mainQueue = Volley.newRequestQueue(this.getApplicationContext());
}
CompanyHttpClient.getInstance().setMainQueue(mainQueue);
}
}
In one of my Android apps I'm using a singleton class to manage InApp billing. Inside this class I'm using a list of interfaces to notify responses from the InApp billing server.
Since my app is multi thread I would like this list to be syncronized, so I've done this:
public class InAppPurchasesManager {
...
private List<InAppPurchasesInterface> requestProductsListeners;
...
public static InAppPurchasesManager getInstance() {
if (mInstance == null) {
mInstance = new InAppPurchasesManager();
}
return mInstance;
}
private InAppPurchasesManager() {}
public void initInAppPurchasesManager(Application app) {
ctxt = app.getApplicationContext();
mInstance.requestProductsListeners = Collections.synchronizedList(new ArrayList());
}
...
public void addRequestProductListener(InAppPurchasesInterface listener)
{
synchronized(requestProductsListeners)
{
if(!requestProductsListeners.contains(listener))
requestProductsListeners.add(listener);
}
}
public void removeRequestProductListener(InAppPurchasesInterface listener)
{
synchronized(requestProductsListeners)
{
requestProductsListeners.remove(listener);
}
}
private void responseOnRequestProductsAndPurchase(boolean success)
{
callingIabHelper = false;
synchronized(requestProductsListeners)
{
for(InAppPurchasesInterface listener : requestProductsListeners)
listener.onRequestProductsAndPurchased(success);
return;
}
}
...
public interface InAppPurchasesInterface {
void onServiceStarted(boolean success);
void onRequestProductsAndPurchased(boolean success);
void onRequestProductPurchased(Purchase purchase, int error);
}
}
But I'm still receiveing a lot of crashes due to concurrent access to requestProductListeners.
This is a crash sample:
Fatal Exception: java.util.ConcurrentModificationException
at java.util.ArrayList$ArrayListIterator.next(ArrayList.java:573)
at com.mypackage.managers.InAppPurchasesManager.responseOnRequestProductsAndPurchase(InAppPurchasesManager.java:173)
at com.mypackage.managers.InAppPurchasesManager.access$100(InAppPurchasesManager.java:24)
at com.mypackage.managers.InAppPurchasesManager$2.onQueryInventoryFinished(InAppPurchasesManager.java:145)
at com.mypackage.auxiliary.inappbilling.IabHelper$2$1.run(IabHelper.java:741)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:7329)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
Since I'm not an expert with syncronization and multithreading I'm sure I'm doing something wrong. Should any of the solutions of these questions work? https://stackoverflow.com/a/2120409
Maybe adding "synchronized" to the definition of the methods that use requestProductsListeners?
EDIT:
I finally added synchronized but the crashes due to "java.util.ConcurrentModificationException" keep appearing.
This is my code now:
public class InAppPurchasesManager {
...
private List<InAppPurchasesInterface> requestProductsListeners;
...
public static InAppPurchasesManager getInstance() {
if (mInstance == null) {
mInstance = new InAppPurchasesManager();
}
return mInstance;
}
private InAppPurchasesManager() {}
public void initInAppPurchasesManager(Application app) {
ctxt = app.getApplicationContext();
mInstance.requestProductsListeners = Collections.synchronizedList(new ArrayList());
}
...
public synchronized void addRequestProductListener(InAppPurchasesInterface listener)
{
synchronized(requestProductsListeners)
{
if(!requestProductsListeners.contains(listener))
requestProductsListeners.add(listener);
}
}
public synchronized void removeRequestProductListener(InAppPurchasesInterface listener)
{
synchronized(requestProductsListeners)
{
requestProductsListeners.remove(listener);
}
}
private synchronized void responseOnRequestProductsAndPurchase(boolean success)
{
callingIabHelper = false;
synchronized(requestProductsListeners)
{
for(InAppPurchasesInterface listener : requestProductsListeners)
listener.onRequestProductsAndPurchased(success);
return;
}
}
...
public interface InAppPurchasesInterface {
void onServiceStarted(boolean success);
void onRequestProductsAndPurchased(boolean success);
void onRequestProductPurchased(Purchase purchase, int error);
}
}
What am I missing?
EDIT 2:
To answer the comment of Maxim Blumental I add the following information:
addRequestProductListener method (add a listener to the array) is called in OnResume() method of some fragments which need the products and their prices.
responseOnRequestProductsAndPurchase method (iterate through the array of listeners calling the callback) is called once Google has sent the information about products and their prices.
removeRequestProductListener method (remove the listener from the array) is called once the callback just mentioned has finished.
initInAppPurchasesManager is only called in
public class MyApp extends MultiDexApplication {
#Override
public void onCreate() {
super.onCreate();
InAppPurchasesManager.getInstance().initInAppPurchasesManager(this);
}
I am trying to send an update to my Activity from my GCMServiceListener so, I am using RxJava/RxAndroid And created a BusClass for handling sending and Observers
public class ClientBus {
//private final PublishSubject<Object> _bus = PublishSubject.create();
// If multiple threads are going to emit events to this
// then it must be made thread-safe like this instead
private final Subject<Object, Object> _bus = new SerializedSubject<>(PublishSubject.create());
public void send(Object o) {
_bus.onNext(o);
}
public Observable<Object> toObserverable() {
return _bus;
}
public boolean hasObservers() {
return _bus.hasObservers();
}
}
And in my Application Class I did this to initialize the BusClass
private ClientBus clientBus;
public ClientBus getRxBusSingleton() {
if (clientBus == null) {
clientBus = new ClientBus();
}
return clientBus;
}
In the activity I want to receive the message, I registered a CompositeSubscription and get a reference to my ClientBus class from the Application Class
clientBus = ((MyApplication) getApplicationContext()).getRxBusSingleton();
#Override
protected void onStart() {
super.onStart();
initSubscriptions();
}
#Override
protected void onStop() {
super.onStop();
_subscriptions.unsubscribe();
}
void initSubscriptions() {
_subscriptions = new CompositeSubscription();
_subscriptions.add(clientBus.toObserverable().subscribe(new Action1<Object>() {
#Override
public void call(Object event) {
Log.e("New Event", "Event Received");
if (event instanceof MyGcmListenerService.Message) {
String msg = ((MyGcmListenerService.Message) event).getMessage();
if (msg.equals("Update Available")) {
scheduleArrayList = getSchedules();
scheduleAdapter = new ScheduleAdapter(getApplicationContext(), scheduleArrayList, ScheduledUberActivity.this);
scheduledList.setAdapter(scheduleAdapter);
scheduleAdapter.notifyDataSetChanged();
} else if (msg.equals("Refresh")) {
fetchTrips();
}
}
}
}));
}
And from the MyGcmListenerService class I did this when I get a new notification
private void sendRefreshNotif() {
if (clientBus.hasObservers()) {<--It enters the if cause the Log prints. But, the activity doesn't get the message
Log.e("Obervers", "Observers aren't null");
clientBus.send(new Message("Refresh"));
}
}
What I don't understand is why isn't it working here? I use it to interact between activities and fragments. I closed my application to check if the notification comes in, It'll enter this block if (clientBus.hasObservers()) { but it didn't and starting the app and testing the Observer, it notices there's an active Observer. Any help? Thanks.
It seems like you used different instances of the ClientBus class in CompositeSubscription and MyApplication.
Try to make a singleton from ClientBus class, it works fine for me.
public class ClientBus {
public ClientBus(SingletonAccessor accessor) {}
private static ClientBus instance;
private static class SingletonAccessor{}
public static ClientBus getInstance() {
if (instance == null) instance = new ClientBus(new SingletonAccessor());
return instance;
}
private final Subject<Object, Object> mBus = new SerializedSubject<>(PublishSubject.create());
public void send(Object o) {
mBus.onNext(o);
}
public Observable<Object> toObserverable() {
return mBus;
}
public boolean hasObservers() {
return mBus.hasObservers();
}
}
I recently converted my application from using async tasks to rxjava. Now, my espresso tests no longer wait for my data calls to complete due to espresso not having an idling resources for rxjava. I noticed that you can make custom idling resources but I can't figure out how to make it work with rxJava Schedulers, Scheduler.io specifically. Any help/best practice would be greatly appreciated.
Here is how I solved the problem:
IdlingResource implementation:
public class IdlingApiServiceWrapper implements MyRestService, IdlingResource {
private final MyRestService api;
private final AtomicInteger counter;
private final List<ResourceCallback> callbacks;
public IdlingApiServiceWrapper(MyRestService api) {
this.api = api;
this.callbacks = new ArrayList<>();
this.counter = new AtomicInteger(0);
}
public Observable<MyData> loadData(){
counter.incrementAndGet();
return api.loadData().finallyDo(new Action0() {
#Override
public void call() {
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
counter.decrementAndGet();
notifyIdle();
}
});
}
});
}
#Override public String getName() {
return this.getClass().getName();
}
#Override public boolean isIdleNow() {
return counter.get() == 0;
}
#Override public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
callbacks.add(resourceCallback);
}
private void notifyIdle() {
if (counter.get() == 0) {
for (ResourceCallback cb : callbacks) {
cb.onTransitionToIdle();
}
}
}
}
and here is my test:
public class MyActivityTest extends ActivityInstrumentationTestCase2<MyActivity> {
#Inject
IdlingApiServiceWrapper idlingApiWrapper;
#Override
public void setUp() throws Exception {
//object graph creation
super.setUp();
getActivity();
Espresso.registerIdlingResources(idlingApiWrapper);
}
public void testClickOpenFirstSavedOffer() throws Exception {
onData(is(instanceOf(DataItem.class)))
.atPosition(0)
.perform(click());
}
}
I used Dagger for dependency injection.
Wrote a little integration piece between RxJava Plugins and Espresso. Hope this helps someone else.
https://gist.github.com/digitalbuddha/d886eae1578bca78b9bf
Edit:
There is a much easier way to accomplish this task. Add the following rule to your tests
public class AsyncTaskSchedulerRule implements TestRule {
final Scheduler asyncTaskScheduler = Schedulers.from(AsyncTask.THREAD_POOL_EXECUTOR);
#Override
public Statement apply(Statement base, Description description) {
return new Statement() {
#Override
public void evaluate() throws Throwable {
RxJavaHooks.setOnIOScheduler(scheduler -> asyncTaskScheduler);
RxJavaHooks.setOnComputationScheduler(scheduler -> asyncTaskScheduler);
RxJavaHooks.setOnNewThreadScheduler(scheduler -> asyncTaskScheduler);
try {
base.evaluate();
} finally {
RxJavaHooks.reset();
}
}
};
}
}
I am currently using this implementation. Its easier and works very well for me so far: https://github.com/rosshambrick/RxEspresso
I want to use RoboGuice in a standard Android JUnit instrumentation test case and override one piece of my app's actual wiring with a mock for testing. I can't find anything online that explains how to do this as all of my search results go to Robolectric with RoboGuoice. I am not using Robolectric nor can I use it in my app for various reasons. Has anyone wired an app with RoboGuice and injected mocks for standard Android Intrumentation test cases?
I'm using the Roboguice 3 and I solved this problem with the following setup and teardown methods within the standard ActivityInstrumentationTestCase2.
Obviously you would need to replace new TestModule() in the snippet below with your own test module class.
#Override
protected void setUp() throws Exception {
super.setUp();
Application app = (Application)getInstrumentation().getTargetContext()
.getApplicationContext();
RoboGuice.getOrCreateBaseApplicationInjector(app, RoboGuice.DEFAULT_STAGE,
Modules.override(RoboGuice.newDefaultRoboModule(app))
.with(new TestModule()));
getActivity();
}
#Override
protected void tearDown() throws Exception {
RoboGuice.Util.reset();
super.tearDown();
}
I've managed to get it work in a simple usage way, you just bind dependencies inside rule using builder and may forget about them later, it will do everything by itself. You may think it's over engineered, but it's realy good for reusing if tyou have a many test classes with robo guice dependencies inside.
Usage in test classes looks like:
#Rule
public InjectWithMocksRule injectWithMocksRule = new InjectWithMocksRule(
this,
() -> new InjectRule
.BindingBuilder()
.add(MyClass.class, mockedClassImpl)
.add(SomeInterface.class, mockedInterfaceImpl));
I wrote helper class TestBindingModule:
public class TestBindingModule extends AbstractModule {
private HashMap<Class<?>, Object> bindings = new HashMap<Class<?>, Object>();
#Override
#SuppressWarnings("unchecked")
protected void configure() {
Set<Entry<Class<?>, Object>> entries = bindings.entrySet();
for (Entry<Class<?>, Object> entry : entries) {
bind((Class<Object>) entry.getKey()).toInstance(entry.getValue());
}
}
public void addBinding(Class<?> type, Object object) {
bindings.put(type, object);
}
public void addBindings(HashMap<Class<?>, Object> bindings) {
this.bindings.putAll(bindings);
}
public static void setUp(Object testObject, TestBindingModule module) {
Module roboGuiceModule = RoboGuice.newDefaultRoboModule(RuntimeEnvironment.application);
Module testModule = Modules.override(roboGuiceModule).with(module);
RoboGuice.getOrCreateBaseApplicationInjector(RuntimeEnvironment.application, RoboGuice.DEFAULT_STAGE, testModule);
RoboInjector injector = RoboGuice.getInjector(RuntimeEnvironment.application);
injector.injectMembers(testObject);
}
public static void tearDown() {
Application app = RuntimeEnvironment.application;
DefaultRoboModule defaultModule = RoboGuice.newDefaultRoboModule(app);
RoboGuice.getOrCreateBaseApplicationInjector(app, RoboGuice.DEFAULT_STAGE, defaultModule);
}
}
Than I use custom Rule to make it work easy:
public class InjectRule implements TestRule {
public interface BindingBuilderFactory {
BindingBuilder create();
}
public static class BindingBuilder {
private HashMap<Class<?>, Object> bindings = new HashMap<>();
public BindingBuilder add(Class<?> dependencyClass, Object implementation) {
bindings.put(dependencyClass, implementation);
return this;
}
HashMap<Class<?>, Object> buildBindings() {
return this.bindings;
}
}
private Object target;
private BindingBuilderFactory bindingBuilderFactory;
public InjectRule(Object target, BindingBuilderFactory bindingBuilderFactory) {
this.target = target;
this.bindingBuilderFactory = bindingBuilderFactory;
}
private void overrideTestInjections(Object target) {
TestBindingModule module = new TestBindingModule();
module.addBindings(this.bindingBuilderFactory.create().buildBindings());
TestBindingModule.setUp(target, module);
}
#Override
public Statement apply(Statement base, Description description) {
return new StatementDecorator(base);
}
private class StatementDecorator extends Statement {
private Statement baseStatement;
StatementDecorator(Statement b) {
baseStatement = b;
}
#Override
public void evaluate() throws Throwable {
before();
try {
baseStatement.evaluate();
} catch (Error e) {
throw e;
} finally {
after();
}
}
void after() {
TestBindingModule.tearDown();
}
void before() {
overrideTestInjections(target);
}
}
}
Also you may want to init mocks with #Mock annotation inside of your test classes, so you need another custom rule:
public class MockitoInitializerRule implements TestRule {
private Object target;
public MockitoInitializerRule(Object target) {
this.target = target;
}
#Override
public Statement apply(Statement base, Description description) {
return new MockitoInitializationStatement(base, target);
}
private class MockitoInitializationStatement extends Statement {
private final Statement base;
private Object test;
MockitoInitializationStatement(Statement base, Object test) {
this.base = base;
this.test = test;
}
#Override
public void evaluate() throws Throwable {
MockitoAnnotations.initMocks(test);
base.evaluate();
}
}
}
And, finaly, you want to combine them to mock mocks first and then set them as dependencies:
public class InjectWithMocksRule implements TestRule {
private final RuleChain delegate;
public InjectWithMocksRule(Object target, InjectRule.BindingBuilderFactory bindingBuilderFactory) {
delegate = RuleChain
.outerRule(new MockitoInitializerRule(target))
.around(new InjectRule(target, bindingBuilderFactory));
}
#Override
public Statement apply(Statement base, Description description) {
return delegate.apply(base, description);
}
}