lube / nfc-react-native

69 stars 35 forks source link

"Operación cancelada" when pressing button for getCardId() call #3

Closed Raimmaster closed 7 years ago

Raimmaster commented 7 years ago

I'm trying to test this module in an RN app we're working on. I have no idea how to implement a dynamic event so it detects the card when it is approached. Regardless, I'm trying to force it by binding it to the onPress event of a button we have. This is the specific commit I've made (and by extension, you can check the repo of the app): https://github.com/ebenezer-unitec/ReactNativeSBO/commit/2f430c34e9b3bde88c0469f49713cea0b8a4d4d9

Any help, direction as to how the function should be implemented would be greatly appreciated.

Y también podemos continuar esto en españo, si llega a ser más fácil para ambos, jaja.

lube commented 7 years ago

We can continue in english or spanish :stuck_out_tongue_closed_eyes:

So, what we do is trigger the read/write/id functions on lifecycle (or sagas) in some routes.

You could easily change the module to work with an event emitter if that sounds more comfortable, you should think a little bit more about the api but it should'nt be so much work from this example.

Raimmaster commented 7 years ago

I have never done such an event emitter in RN; point is, I have my own NFC app in Android (native SDK), and it detects the card automatically. My problem is, I have no idea how to call that event automatically, how to emit such an event when the NFC card is approached. Any direction that you show me for this without necessarily giving code out would help.

Raimmaster commented 7 years ago

Also, I have no idea what you mean by lifecycle/cycle in this case.

lube commented 7 years ago

The thing is, the event is going to trigger automatically whenever a tag is approached, so what you want to do is handling those events ('intents' on android language). In order to handle those events, I thought about two ways of aproaching the problem.

1) Event Emmiter

DeviceEventEmitter('some-event', function () {} / callback function when event happens /)

2) Promise

readTag(some, data, for, the, module).then(function() {} /data/)

The event emitter looks like a cleaner way because you need to constantly have a promise ready to fetch the intent, the problem is, you have many operations with a tag (read, write, etc) and I couldnt find a comfortable api with a event emiter.

Lifecycle refers to normal react components hooks (componentDidMount, etc)

Raimmaster commented 7 years ago

I think I'm getting the hang of what you mean. I don't have to write to the tag; I only need to read from it, and that's enough. In that case, what I need is the following, right? https://facebook.github.io/react-native/docs/native-modules-android.html#sending-events-to-javascript

Basically, add a listener to the current window (in such a case, this), and then let that event be called when it detects an NFC tag, right?

lube commented 7 years ago

Exactly you can use the readTag promise, or modify this module to use a eventemitter (remember you still need a way to pass keys + sectors/blocks to the event emitter function) (maybe an initialization function)

The main java file is 100 lines should'nt be really too hard to follow it ignoring my lame java skills

Raimmaster commented 7 years ago

I've never used a promise in such a callback function.

componentWillMount: function() { DeviceEventEmitter.addListener('keyboardWillShow', getCardId() .then((card) => { console.log(card) }).catch((err) => { console.log(err) }) });

Would it be like that, or what is the correct way of adding the listener of the fulfilled promise?

lube commented 7 years ago

Mostly right just

componentWillMount: function () { getCardId().then((card) => console.log(card))
                                             .catch((err) => console.log(err)) }
Raimmaster commented 7 years ago

Thanks, I'm going to try it out now.

By the way, in your README, you state that we have to create an XML file in the res folder, yet I'm not sure what should the name of the file be (I tried with "nfc_tech_filter", but it when I generated the Android project, it said that it couldn't find such a file from the metadata section of the Manifest).

atasmohammadi commented 7 years ago

Have you find a solution? I also need to read the card id but i don't know how.

joostmakaay commented 7 years ago

Hey, i've implemented the solution you posted above (componentWillMount), the NFC-listener will trigger(reload the scene) when I use a Tag, but the GetCardId() event won't return anything besides Operation Cancelda, do you have any idea what could cause this? Or could you post an example project online? @Lube

atasmohammadi commented 7 years ago

@Lube it would be great if you could add deviceventemitter to your module. an event listener which triggers when i tag is readed.

fuxianwei commented 7 years ago

I am also in study react native read NFC tags, this project gives me some ideas, but I suggest that the error "Operaci n cancelada" when directly with the getCardId library to read the information, when you close NFC equipment in react native prison in how to listen to this event, I hope to get your help. Thank you @Lube

joostmakaay commented 7 years ago

@ataomega @fuxianwei hey guys, if you're still struggling with this, i've solved my problem by setting the android:launchMode=singleInstance of the activity. This way the library will return more than only the warning 'Operation Cnacelda'

atasmohammadi commented 7 years ago

Would it be possible to paste your code here? You mean you were able to get nfc card id?

Thanks in Advance

On Tue, Jan 10, 2017, 22:40 joostmakaay notifications@github.com wrote:

@ataomega https://github.com/ataomega @fuxianwei https://github.com/fuxianwei hey guys, if you're still struggling with this, i've solved my problem by setting the android:launchMode=singleInstance of the activity. This way the library will return more than only the warning 'Operation Cnacelda'

— You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub https://github.com/Lube/nfc-react-native/issues/3#issuecomment-271706506, or mute the thread https://github.com/notifications/unsubscribe-auth/AQD7QaNykh659iwkZRp0qaYgTQ6xMcrDks5rQ_regaJpZM4LQBvO .

joostmakaay commented 7 years ago

@ataomega , The solution we've found is definitely not the way it should be done, but we needed working NFC for react-native asap.

I've removed all the functions from the NfcReactNativeModule.java except for the GetCardID: ` public NfcReactNativeModule(ReactApplicationContext reactContext) { super(reactContext); this.nfcCode = ""; this.reactContext = reactContext; this.reactContext.addActivityEventListener(this); this.operation = OP_NOT_READY; }

@Override
public void onNewIntent(Intent intent) {
    if (this.operation.equals(OP_NOT_READY)) {
        return;
    }

    MifareClassic tag = MifareClassic.get( (Tag)intent.getParcelableExtra(NfcAdapter.EXTRA_TAG));
    try {
        tag.connect();
        ByteBuffer bb = ByteBuffer.wrap(tag.getTag().getId());
        this.nfcCode = byteArrayToHexString(bb.array());
    } catch (Exception ex) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        this.tagPromise.reject(sw.toString(), ex.getMessage());
    } finally {
        this.operation = OP_NOT_READY;
    }
}

` on a new Intent the cardID will be written to a String, in the getCardId function I do the following:

` @ReactMethod public void getCardId(Promise promise) { this.cancelOperation(); this.operation = OP_ID; this.tagPromise = promise; }

private void cancelOperation () {
    if (!this.operation.equals(OP_NOT_READY)) {
        this.tagPromise.reject(this.nfcCode);
        this.nfcCode = "";
        this.operation = OP_NOT_READY;
    }
}

` The tagPromise.Reject will return an error the the GetCardId-call I did in my ReactNative project, since I couldn't get tagPromise.Resolve to work (so it would return in the promise of the GetCardId-call.

Here is my AndroidManifest.xml

`<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.exampleapp" android:versionCode="1" android:versionName="1.0">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

<uses-sdk
    android:minSdkVersion="16"
    android:targetSdkVersion="22" />

<application
  android:name=".MainApplication"
  android:allowBackup="true"
  android:label="@string/app_name"
  android:icon="@mipmap/ic_launcher"
  android:theme="@style/AppTheme">
  <activity
    android:name=".MainActivity"
    android:launchMode="singleInstance"
    android:label="@string/app_name"
    android:screenOrientation="landscape"
    android:configChanges="keyboard|keyboardHidden|orientation|screenSize">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.nfc.action.TECH_DISCOVERED" />
    </intent-filter>

    <meta-data
        android:name="android.nfc.action.TECH_DISCOVERED"
        android:resource="@xml/nfc_tech_filter" />

    <intent-filter>
        <action android:name="android.nfc.action.TAG_DISCOVERED" />
    </intent-filter>
  </activity>

  <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>

`

and since I couldn't get a eventlistener working, i've made timer function that will execute GetCardId every x miliseconds, so nfcCode will be retrieved if it's set:

` componentDidMount: function(){ this.startNFCloop();
},

startNFCloop: function(){
    var self = this;
    setTimeout(function() {
    if (!self.isMounted()) { return; } // abandon 
        //self.callback(); // do it once and then start it up ...
    self._timer = setInterval(self.nfcCallback.bind(self), 1000);
    }, 1000);    
},

nfcCallback: function(){
   getCardId().then((card) => {
    }).catch((err) => {
        // NFCcode isset, do something
        if (err.message > "") {
            this.login(err.message);
        }
    });
},`

I don't recommend using this code, since it's very hacky and not the way a module should be used.

atasmohammadi commented 7 years ago

Thanks for sharing. Im in the same situation and im out of time. I will try it. Thanks again.

On Tue, Jan 10, 2017, 23:02 joostmakaay notifications@github.com wrote:

@ataomega https://github.com/ataomega , The solution we've found is definitely not the way it should be done, but we needed working NFC for react-native asap.

I've removed all the functions from the NfcReactNativeModule.java except for the GetCardID: ` public NfcReactNativeModule(ReactApplicationContext reactContext) { super(reactContext); this.nfcCode = ""; this.reactContext = reactContext; this.reactContext.addActivityEventListener(this); this.operation = OP_NOT_READY; }

@Override public void onNewIntent(Intent intent) { if (this.operation.equals(OP_NOT_READY)) { return; }

MifareClassic tag = MifareClassic.get( (Tag)intent.getParcelableExtra(NfcAdapter.EXTRA_TAG));
try {
    tag.connect();
    ByteBuffer bb = ByteBuffer.wrap(tag.getTag().getId());
    this.nfcCode = byteArrayToHexString(bb.array());
} catch (Exception ex) {
    StringWriter sw = new StringWriter();
    PrintWriter pw = new PrintWriter(sw);
    this.tagPromise.reject(sw.toString(), ex.getMessage());
} finally {
    this.operation = OP_NOT_READY;
}

}

` on a new Intent the cardID will be written to a String, in the getCardId function I do the following:

` @ReactMethod public void getCardId(Promise promise) { this.cancelOperation(); this.operation = OP_ID; this.tagPromise = promise; }

private void cancelOperation () { if (!this.operation.equals(OP_NOT_READY)) { this.tagPromise.reject(this.nfcCode); this.nfcCode = ""; this.operation = OP_NOT_READY; } }

` The tagPromise.Reject will return an error the the GetCardId-call I did in my ReactNative project, since I couldn't get tagPromise.Resolve to work (so it would return in the promise of the GetCardId-call.

Here is my AndroidManifest.xml

`

<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="22" />

<application android:name=".MainApplication" android:allowBackup="true" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:theme="@style/AppTheme"> <activity android:name=".MainActivity" android:launchMode="singleInstance" android:label="@string/app_name" android:screenOrientation="landscape" android:configChanges="keyboard|keyboardHidden|orientation|screenSize">

<intent-filter>
    <action android:name="android.nfc.action.TECH_DISCOVERED" />
</intent-filter>

<meta-data
    android:name="android.nfc.action.TECH_DISCOVERED"
    android:resource="@xml/nfc_tech_filter" />

<intent-filter>
    <action android:name="android.nfc.action.TAG_DISCOVERED" />
</intent-filter>

`

and since I couldn't get a eventlistener working, i've made timer function that will execute GetCardId every x miliseconds, so nfcCode will be retrieved if it's set:

`componentDidMount: function(){ this.startNFCloop(); },

startNFCloop: function(){ var self = this; setTimeout(function() { if (!self.isMounted()) { return; } // abandon //self.callback(); // do it once and then start it up ... self._timer = setInterval(self.nfcCallback.bind(self), 1000); }, 1000); },

nfcCallback: function(){ getCardId().then((card) => { }).catch((err) => { // NFCcode isset, do something if (err.message > "") { this.login(err.message); } }); },`

I don't recommend using this code, since it's very hacky and not the way a module should be used.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/Lube/nfc-react-native/issues/3#issuecomment-271712095, or mute the thread https://github.com/notifications/unsubscribe-auth/AQD7Qav-xm0qVYXHFjX3TIVRTnqUp3V_ks5rQ__sgaJpZM4LQBvO .

Raimmaster commented 7 years ago

Thanks for the heads up, @joostmakaay. Any direction of working implementations are welcome.

atasmohammadi commented 7 years ago

@joostmakaay Please take a look at here : http://stackoverflow.com/questions/41615851/react-native-android-nfc-getting-card-id

I've edited it to use sendEvent

Raimmaster commented 7 years ago

@joostmakaay, I've tried to implement your solution in this repo https://github.com/ebenezer-unitec/ReactNativeSBO/, using it specifically in this file: https://github.com/ebenezer-unitec/ReactNativeSBO/blob/NFC_Test/src/components/scanner/scanningView.js

Now, I don't know if I've been implementing it well, as I've been editing the other files you said (and those have been added to the .gitignore, due to them being in the .node_modules directory). Would you mind helping us notice what is that we're doing wrong in implementing your solution? Please.

joostmakaay commented 7 years ago

@Raimmaster checked your repo quick, first thing I noticed is that you didn't modify AndroidManifest/MainApplication files, if you don't add the reference to the NFC node_module the code won't get compiled, and by adding the NFC-intentfilter to the AndroidManifest, the app can't detect a NFC trigger from Android. if you copy those things from my example, i think you will be a step further in getting it working.

Raimmaster commented 7 years ago

I've added all the configurations as you said, and while it has been a progress (now it compiles correctly, and my cellphone asks whether to use the "New tag detected" action Android already has implemented, or the application I'm developing), it still does not detect the ID, apparently. It's pretty much an infinite alert of "Operación Cancelada", @joostmakaay. You may check the repo again to see if we've missed something again. Same branch, NFC_Test.

Raimmaster commented 7 years ago

This is my NfcReactiveModule.java file, by the way: http://pastebin.com/Hkw2Wrjz

lube commented 7 years ago

Hey guys, sorry for being absent in this discussion, I made an API overhaul to work with event emmiters and allow multiple readings and writes with only one intent, I also included a sample project so you cant try it.