I'm using Phonegap to develop an app. I've downloaded a camera plugin, however, I'd like to make a Javascript call from within the plugin.
In the Java file for the camera plugin I have done the following;
private class sendJS extends CordovaActivity {
public void sendcommand() {
this.sendJavascript("alert('1337')");
}
}
#Override
public void onClick(View v) {
sendJS test = new sendJS();
test.sendcommand();
}
However, when the onclick is triggered nothing happens...
I've also tried super.sendJavascript() and super.loadUrl() but it didn't work.
Thanks.
You have two ways to comunicate to your javascript code. The first one is injecting code to webview via .loadUrl(...) method. The second one is via a callback in response to a javascript->native-plugin(java) call.
You can see callback response in execYourJavaMethod() and injecting in sendcommand()
private class sendJS extends CordovaActivity {
#Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
// Implement here your calls from javascript
boolean result = false;
if ("yourJavaMethod".equals(action)) {
JSONObject options = args.optJSONObject(0);
result = execYourJavaMethod(options, callbackContext);
}
return result;
}
public boolean execYourJavaMethod(JSONObject options, CallbackContext callbackContext) {
// This will inject an event to your javascript code
this.sendcommand();
boolean iWantToCallSuccessCallbackWithData = false;
if (iWantToCallSuccessCallbackWithData) {
// This will call your success callback with some data
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, "Your data back to javascript"));
} else {
// This will call the success callback with no data
callbackContext.success();
}
return true;
}
public void sendcommand() {
String event = String.format("javascript:cordova.fireDocumentEvent('yourEventHere', { 'param1': '%s' });", "some string for param1");
this.webView.loadUrl(event);
}
}
From javascript side you should register the listener for your event:
document.addEventListener('yourEventHere', function(e) {
alert(JSON.stringify(e));
});
To comunicate to your Java plugin:
myPlugin.doSomethingInJava = function (successCallback, failureCallback) {
cordova.exec(successCallback, failureCallback, 'sendJS', 'yourJavaMethod', []);
};
Related
I am using mvvm architecture I would like to notify view when volley post request is successful, what i could do is to instantiate ViewModel in appRepository class and then post values to a liveData, but i guess that's not a good approach as I haven't seen a similar practice. Can anyone suggest me a good approach to return my response to ui, or at least notify that post request has been successful.
From fragment/View I trigger this method
// save data to api
checkInViewModel.updateEventPersonEntity(eventPersonsEntity);
ViewModel forwards it to apprespository
public void updateEventPersonEntity(EventPersonsEntity eventPersonsEntity) {
mRepository.updateEventPersonEntity(eventPersonsEntity);
}
AppRepository.Java class
public void updateEventPersonEntity(EventPersonsEntity eventPersonsEntity) {
executor.execute(() -> {
// mDb.eventPersonsDao().update(eventPersonsEntity);
if (isNetworkAvailable(context)) {
post_updateEventPersonEntity(eventPersonsEntity);
}
});
}
private void post_updateEventPersonEntity(EventPersonsEntity eventPersonsEntity) {
Map<String, Object> params = new HashMap<>();
params.put("EventPersonId", eventPersonsEntity.getEventPersonId());
params.put("EventId", eventPersonsEntity.getEventId());
params.put("PersonId", eventPersonsEntity.getPersonId());
params.put("CashStart", parseDoubleToGerman(eventPersonsEntity.getCashStart()));
params.put("CashEnd", parseDoubleToGerman(eventPersonsEntity.getCashEnd()));
params.put("StartingTime", String.valueOf(eventPersonsEntity.getStartingTime()));
params.put("EndingTime", String.valueOf(eventPersonsEntity.getEndingTime()));
params.put("isChekcedIn", eventPersonsEntity.getIsCheckedIn());
params.put("isChekcedOut", eventPersonsEntity.getIsCheckedOut());
JSONObject objRegData = new JSONObject(params);
String eventPersonApi = APP_URL.EVENT_PERSONS_API + eventPersonsEntity.getEventPersonId();
RequestQueueSingleton.getInstance(context).objectRequest(eventPersonApi, Request.Method.PUT, this::onSuccess_updateEventPersonEntity, this::onError, objRegData);
}
private void onError(VolleyError error) {
Log.d(APP_REPOSITORY_TAG, "requestError: " + error);
}
private void onSuccess_updateEventPersonEntity(JSONObject jsonObject) {
// notify ui
}
You can do this same as you did for your success response logic in repository. Simply create new callback interface:
interface OnEventUpdatedListener{
void eventUpdated();
}
Then, update your method to look like this, passing the listener to the actual method that does the work:
public void updateEventPersonEntity(EventPersonsEntity eventPersonsEntity, OnEventUpdatedListener listener) {
mRepository.updateEventPersonEntity(eventPersonsEntity, listener);
}
Pass this inside your:
if (isNetworkAvailable(context)) {
post_updateEventPersonEntity(eventPersonsEntity, listener);
}
After that, in your onSuccess() method simply call:
private void onSuccess_updateEventPersonEntity(JSONObject jsonObject) {
listener.eventUpdated();
}
Finally, you will have the info when the update happens, in the calling site, if you call your repository like this:
updateEventPersonEntity(null, new OnEventUpdatedListener() {
#Override
public void EventUpdated() {
// Do your logic here
}
});
Is there any way to implement an interface in dart/flutter without having to use a class?
Currently, how I implement it is with the code below
class _UserSignupInterface extends _SignupSelectUsernamePageState
implements UserSignupInterface {
#override
void onSuccess() {
_navigateToUserPage();
}
#override
void onError() {
setState(() {
_isSignupClickable = true;
});
}
}
_attemptSignup() {
UserSingleton userSingletonInstance = UserSingleton().getInstance();
UserSignupInterface _userSignupInterface = _UserSignupInterface();
UserSingleton().getInstance().user.username = _username;
UserLoginController.attemptSignup(_userSignupInterface,
userSingletonInstance.user, userSingletonInstance.userDetail, _groupID);
}
However, I would like to implement these interface methods without having to use a class, just as I would in java. Something that would look like the code below.
UserController.attemptSignup(context, new UserSignupRequest() {
#Override
public void onSuccess(User user, UserDetail userDetail, Group group) {
btnContinueWithFacebook.setEnabled(true);
Intent intent = new Intent(context, ScoopActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
progressBar.setVisibility(View.GONE);
startActivity(intent);
}
#Override
public void onFail() {
Log.d(APP.TAG, "Signup request has failed");
btnContinueWithFacebook.setEnabled(true);
progressBar.setVisibility(View.GONE);
/**
* TODO:: Notify user of signup attempt failure
*/
}
}, user, userDetail, group_id);
There is no such feature in Dart. In order to implement an interface, you have to declare a class.
The alternatives is to define the API to accept individual functions instead of a single object, or to declare a helper class which takes the behavior of the necessary methods as constructor arguments.
Example:
class _UserSignupInterface extends _SignupSelectUsernamePageState
implements UserSignupInterface {
void Function(_UserSingupInterface self) _onSuccess;
void Function(_UserSingupInterface self) _onError;
_UserSignupInterface(this._onSuccess, this._onError);
#override
void onSuccess() {
_onSuccess(this);
}
#override
void onError() {
_onError(this);
}
}
Then you can call it as:
... _UserSignupInterface((self) {
self._navigateToUserPage();
}, (self) {
self.setState(() {
self._isSignupClickable = true;
});
})
It's not as pretty as Java, admittedly.
I know this question already has an answer but I would like to add a more neater implementation close to Java inline interface which I normally use.
First, we have the class which acts as our interface:
class HttpRequestCallback {
/// Called when http request is completed
final void Function() onCompleted;
/// Called when http request is successful
/// * [message] is a dynamic object returned by the http server response
final void Function(dynamic message) onSuccess;
/// Called when http request fail
/// * [message] is a dynamic object returned by the http server response
final void Function(dynamic message) onError;
HttpRequestCallback(
{required this.onCompleted,
required this.onSuccess,
required this.onError});
}
Secondly, we have a function that expects the interface as parameter:
Future<void> login(LoginModel model, {HttpRequestCallback? callback}) async {
var response = await httpClient.doPost(app_constants.ApiEndpoints.Login,
body: model.toJson());
// Api request completed
callback?.onCompleted();
if (response.success) {
// Api request successful
callback?.onSuccess(LoginResponseModel.fromJson(
response.message as Map<String, dynamic>));
} else {
// Api request failed
callback?.onError(response.message);
}
}
Finally, we call the function passing our interface as an argument:
...
apiService.login(loginModel,
callback: HttpRequestCallback(
onCompleted: () {
//...
},
onSuccess: (message) {
//...
},
onError: (message) {
//...
}
));
...
I think you are looking for anonymous class in Dart, but it's not supported.
If i understood well what you are trying to do, you can achieve something similar by passing function as parameter in this way:
enum ResultLogin { OK, ERROR }
class Login {
Function _listener; // generic function
Login(listener) {
_listener = listener;
}
void run(){
ResultLogin result = *DO_YOUR_LOGIN_FUNCTION*;
_listener(result);
}
}
class Main {
void doLogin(){
Login myLogin = new Login((ResultLogin result){
switch(result){
case OK:
print("OK");
break;
case ERROR:
print("ERROR");
break;
default:
break;
}
}
);
}
}
In this way you can handle your result and refresh some widget state according to your needs.
In my Android app, I'd like to implement success and error callbacks for when I get reading passages from my backend. In iOS, it would look like this:
In my Passage.h:
-(void)getPassagesWithSuccessCallback:(void (^)(NSArray<Passage *> *))success errorCallback:(void (^)(NSString *))errorString;
In my Passage.m:
-(void)getPassagesWithSuccessCallback:(void (^)(NSArray<Passage *> *))success errorCallback:(void (^)(NSString *))errorString {
MyApiInterface* api = [MyApiInterface sharedInstance];
[api sendGetRequestTo:#"passages" successCallback:[Passage modelListCallback:success] errorCallback:error];
}
In my Android app, I'm using Volley to handle my API requests, but I want to further encapsulate this API interfacing by having a Passage.java class with a public static void method that gets the passages. Something like this:
public static void getPassagesForFirebaseUser(FirebaseUser user, Context context) {
final String url = URL_BASE + "/passages.json" + "?auth=" + user.getToken(false);
final JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, url, null, new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
// convert JSON into ArrayList<Passage> object
// pass on this array of Passages in the success completion listener of the method that called this
// just like iOS does success(passages)
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
// convert error to string
// pass on this errorString in the error completion listener of the method that called this
// just like iOS does error(errorString)
}
});
Volley.newRequestQueue(context).add(request);
}
Is there any way to get this kind of implementation flow?
You can use an Interface
public interface ICallbacks {
public void onResponse(JSONObject response);
public void onError(VolleyError error);
}
Then in your routine code just put a new instance of Callbacks (depending on ide that you work could autogenerate the methods)
public static void getPassagesForFirebaseUser(FirebaseUser user,
Context context, ICallbacks events) {
//here code and call ICallbacks methods
if(result){ events.onResponse(response); }
if(error){ events.onError(err); }
}
ultimately you can call the method with :
getPassagesForFirebaseUser(user, context, new ICallbacks(){
#Override
public void onResponse(JSONObject response){
//Success !!!
}
#Override
public void onError(VolleyError response){
//Error !!!
}
});
Sorry for my English, hope this help !
My application uses Cordova. I need to capture a keypress in my app and then call a Java function in my Cordova app, like so:
$(document).on('keypress', function() {
// call mySpecialFunction() Java function here
});
and then the Cordova app's main activity:
public class EndPipe extends CordovaActivity
{
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
super.init();
super.loadUrl(Config.getStartUrl());
}
public void mySpecialFunction() {
// some Java code here
}
}
How can I accomplish this?
you can try this one
Firstly you need to declare your custom plugin in config.xml. You can found this file in res > xml folder.
<feature name="CustomPlugin">
<param name="android-package" value="com.Phonegap.CustomPlugin" />
</feature>
Then you need to implement plugin by using Java- code
public class CustomPlugin extends CordovaPlugin {
#Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext)
throws JSONException {
if (action.equals("sayHello")){
try {
String responseText = "Hello world, " + args.getString(0);
callbackContext.success(responseText);
} catch (JSONException e){
callbackContext.error("Failed to parse parameters");
}
return true;
}
return false;
}
}
Finally we calling a plugin from javascript
function initial(){
var name = $("#NameInput").val();
cordova.exec(sayHelloSuccess, sayHelloFailure, "CustomPlugin", "sayHello", [name]);
}
function sayHelloSuccess(data){
alert("OK: " + data);
}
function sayHelloFailure(data){
alert("FAIL: " + data);
}
I am writing a GWT app that involves interacting with an external document in an iframe. As a proof of concept, I am trying to attach a click handler to a button.
The following works in javascript
var iframe = document.getElementById("rawJSIFrame");
var doc = iframe.contentDocument;
var body = doc.body;
var button = doc.getElementsByTagName("input").namedItem("submit");
button.onclick = function() {
alert("Clicked!");
};
Trying to do the equivalent in GWT, I did the following:
public void addClickHandlerToSubmitButton(String buttonElementName, ClickHandler clickHandler) {
IFrameElement iframe = IFrameElement.as(frame.getElement());
Document frameDocument = getIFrameDocument(iframe);
if (frameDocument != null) {
Element buttonElement = finder(frameDocument).tag("input").name(buttonElementName).findOne();
ElementWrapper wrapper = new ElementWrapper(buttonElement);
HandlerRegistration handlerRegistration = wrapper.addClickHandler(clickHandler);
}
}
private native Document getIFrameDocument(IFrameElement iframe)/*-{
return iframe.contentDocument;
}-*/;
The following is the ElementWrapper class:
public class ElementWrapper extends Widget implements HasClickHandlers {
public ElementWrapper(Element theElement) {
setElement(theElement);
}
public HandlerRegistration addClickHandler(ClickHandler handler) {
return addDomHandler(handler, ClickEvent.getType());
}
}
The code to find the button works fine but the actual click event handler is not getting invoked. Has anybody had a similar issue before, and how did you resolve it?
Thanks in advance,
Tin
Hilbrand is right about the problem being that the GWT method onAttach() was not called.
I implemented your original solution, adding the following method to ElementWrapper:
public void onAttach() {
super.onAttach();
}
And called added wrapper.onAttach() after the ElementWrapper is created. Works like a charm!
I expect the problem is that the GWT method onAttach() is not called when you use the wrapping as in your first example. You can try to use the static wrap method on the Button widget. Although to use this the input must be of type button. Or have a look at the implementation of the wrap method. Here is the modified code when using the wrap method:
Element buttonElement = finder(frameDocument).tag("input").name(buttonElementName).findOne();
Button button = Button.wrap(buttonElement);
HandlerRegistration handlerRegistration = button.addClickHandler(clickHandler);
After researching this further, I found that the iframe is irrelevant. The same behaviour doesn't work on a normal button on the host page.
I basically fixed it by using JSNI to replicate part of GWT's event handling mechanism. The following works:
Element buttonElement = DOM.getElementById("externalButton");
new CustomElementWrapper(buttonElement).addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
Window.alert("GWT hooked into button");
}
});
Where CustomElementWrapper is:
public class CustomElementWrapper extends Widget implements HasClickHandlers {
private ClickEventManager clickEventManager;
public CustomElementWrapper(Element theElement) {
setElement(theElement);
clickEventManager = new ClickEventManager(theElement);
}
public HandlerRegistration addClickHandler(ClickHandler handler) {
//The 'right' way of doing this would be the code below. However, this doesn't work
// A bug in GWT?
//
// return addDomHandler(handler, ClickEvent.getType());
return clickEventManager.registerClickHandler(handler);
}
void invokeClickHandler() {
clickEventManager.invokeClickHandler();
}
public boolean isClickHandlerRegistered() {
return clickEventManager.isClickHandlerRegistered();
}
}
Finally, the ClickEventManager, where the actual work happens is:
public class ClickEventManager {
private boolean clickHandlerRegistered = false;
private ClickHandler clickHandler;
private Element element;
public ClickEventManager(Element element) {
this.element = element;
}
public void invokeClickHandler() {
//This shouldn't really be null but we are bypassing GWT's native event mechanism
//so we can't create an event
clickHandler.onClick(null);
}
public boolean isClickHandlerRegistered() {
return clickHandlerRegistered;
}
HandlerRegistration registerClickHandler(ClickHandler handler) {
clickHandler = handler;
if (!clickHandlerRegistered) {
registerClickHandlerInJS(element);
clickHandlerRegistered = true;
}
return new HandlerRegistration() {
public void removeHandler() {
//For now, we don't support the removal of handlers
throw new UnsupportedOperationException();
}
};
}
private native void registerClickHandlerInJS(Element element)/*-{
element.__clickManager = this;
element.onclick
= function() {
var cm = this.__clickManager;
cm.#com.talktactics.agent2.client.widgets.ClickEventManager::invokeClickHandler()();
}
}-*/;
}
Personally, I hate this solution because I appear to be duplicating GWT's event handling and quite possibly introducing nasty javascript memory leaks. Any ideas on why my first post doesn't work (remembering that the iframe aspect is a red herring), would be appreciated.
Thanks,
Tin
You may find this helpful:
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.AbsolutePanel;
public class DirectPanel extends AbsolutePanel implements HasClickHandlers {
public DirectPanel(Element elem) {
super(elem.<com.google.gwt.user.client.Element> cast());
onAttach();
}
#Override
public HandlerRegistration addClickHandler(ClickHandler handler) {
return addDomHandler(handler, ClickEvent.getType());
}
}
You will then be able to make arbitrary containers into widget containers:
Element root = Document.get().getElementById("target");
DirectPanel p = new DirectPanel(root);
Button register = new Button("Register");
register.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
// ...
}
});
p.add(register);
And bind events to arbitrary elements:
Element root = Document.get().getElementById("target");
DirectPanel p = new DirectPanel(root);
p.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
// ...
}
});
Specifically in your case, try this:
IFrameElement frm = Document.get().createIFrameElement();
Document d = frm.getContentDocument();
NodeList<Element> inputs = d.getElementsByTagName("input");
InputElement target = null;
for(int i = 0; i < inputs.getLength(); ++i) {
Element e = inputs.getItem(0);
if (e.getNodeName().equals("submit")) {
target = InputElement.as(e);
break;
}
}
if (target != null) {
DirectPanel p = new DirectPanel(target);
p.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
// TODO Auto-generated method stub
}
});
}
It's always mystified me that GWT makes doing this so difficult and poorly documented.
Instead of using iframes i suggest you simply make a http request from GWT via com.google.gwt.http.client.RequestBuilder. Like so:
private void getHtml(String url) {
RequestBuilder rb = new RequestBuilder(RequestBuilder.GET, url);
rb.setCallback(new RequestCallback() {
#Override
public void onResponseReceived(Request request, Response response) {
HTMLPanel html = new HTMLPanel(response.getText());
// Now you have a widget with the requested page
// thus you may do whatever you want with it.
}
#Override
public void onError(Request request, Throwable exception) {
Log.error("error " + exception);
}
});
try {
rb.send();
} catch (RequestException e) {
Log.error("error " + e);
}
}
You could use JSNI to reuse your JavaScript piece of code. Your javascript code would call a gwt method on an object that would throw it on behalf of the button in the iframe.
As to why GWT code does not work -- I guess that is because they use some layer on top of regular browser events that probably cannot span more than 1 frame. That's just a guess though. You could file this as a feature/bug request agains GWT team. If I am right your code looks just fine.
Please see my previous answer. A slight modification to your original solution will make it work.