Issues sending data from Android app to Mirror running React app

As the title suggests, I’m having trouble getting my Android app to send data to my Mirror. The issue could also be that the React app running on the mirror isn’t receiving the data, but it’s hard to tell, since there isn’t any official way of debugging the two applications. Any help to find out what’s going on would be appreciated.

Here’s the code I’m using in the Android app:

public class MainActivity extends AppCompatActivity {

    private static final String MIRROR_TEMPLATE = "default";

    private MirrorContextManager mirrorContextManager;
    private Dictionary mirrorDictionary;

    private EditText yourNameEditText;
    private EditText routeEditText;
    private Button saveButton;

    private Toast toast;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        this.initializeMirrorContextManager();
        this.initializeViews();

        this.setupSaveButtonClickListener();
    }

    private void initializeMirrorContextManager() {
        this.mirrorContextManager = MirrorContextManager.createMirrorContextManager(this);
    }

    private void initializeViews() {
        this.yourNameEditText = findViewById(R.id.edit_text_your_name);
        this.routeEditText = findViewById(R.id.edit_text_route);
        this.saveButton = findViewById(R.id.button_save);
    }

    private void setupSaveButtonClickListener() {
        this.saveButton.setOnClickListener(new View.OnClickListener() {
            private MainActivity parent = MainActivity.this;

            @Override
            public void onClick(View view) {
                String name = yourNameEditText.getText().toString();
                String route = routeEditText.getText().toString();
                parent.setupMirrorDictionary(name, route);
                parent.setupMirrorDisplayHandler();
                parent.hideSoftKeyboard();
                parent.displayToast("Settings updated", Toast.LENGTH_LONG);
            }
        });
    }

    private void setupMirrorDictionary(String name, String route) {
        this.mirrorDictionary = new Dictionary();
        this.mirrorDictionary.setTemplate(MIRROR_TEMPLATE);
        this.mirrorDictionary.put("name", name);
        this.mirrorDictionary.put("route", route);
    }

    private void setupMirrorDisplayHandler() {
        this.mirrorContextManager.clearDisplayRequests();
        this.mirrorContextManager.display(this.mirrorDictionary, Zone.NEAR, new DisplayCallback() {
            private MainActivity parent = MainActivity.this;

            @Override
            public void onDataDisplayed(MirrorDevice mirrorDevice) {
                parent.displayToast("Data successfully mirrored!", Toast.LENGTH_LONG);
            }

            @Override
            public void onFinish() {
                parent.displayToast("Device has disconnected from the Mirror", Toast.LENGTH_LONG);
            }

            @Override
            public void onFailure(MirrorException exception) {
                parent.displayToast("Something went wrong", Toast.LENGTH_LONG);
            }

            @Override
            public void onData(JSONObject data) {
                parent.displayToast("Data received from Mirror", Toast.LENGTH_LONG);
            }
        });
    }

    private void displayToast(String message, int length) {
        if (this.toast != null) {
            this.toast.cancel();
        }

        this.toast = Toast.makeText(MainActivity.this, message, length);
        this.toast.show();
    }

    private void hideSoftKeyboard() {
        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
    }
}

And inside of the React component responsible for displaying Mirror data, I’m using componentWillMount() to setup the Mirror API:

  componentWillMount() {
    mirror.init();
    mirror.listen(Mirror.Events.USER_DATA, {
      ondata: (event) => {
        const receievedData = event.data;

        const {
          name,
          route,
        } = receievedData;

        if (name === undefined || route === undefined) {
          return;
        }

        this.setState({
          name,
          route,
        });
      },
      ondisconnect: () => {
        this.setState({
          name: '',
          route: '',
        });
      },
    });
  }

You can debug Android app using Android Studio debugger, no problem with that. Have you received onDataDisplayed callback (have you seen a toast)? Have you tried to write a simple pure JS template and check if it is able to receive data from app? What SDK and Android version number are you using?
I run your React code inside old Mirror React template and it works ok. I don’t know what setState method is doing (maybe there is a problem?) so I used some code to switch to other template when particular route is received:

  setState (data) {
    if (data.route === 'hello') {
      mirror.selectTemplate('default')
    }
  }

Calling this.mirrorContextManager.clearDisplayRequests(); every time you send data will cause SDK to disconnect from Mirror and then reconnect again when display is called. It is not a very efficient way of sending data to Mirror. You may use this method when you are leaving activity.

Other thing you can do is to create simple template that will call:

mirror.selectTemplate('your_template_name_here', {'route':'your_route', 'name': 'your name'})

to check if React is correctly receives and processes template data.

Hi Pober,

Thanks for the useful information.

I called mirror.selectTemplate('your_template_name_here', {'route':'your_route', 'name': 'your name'}) after mirror.listen() and was able to successfully reflect the data in my template, so it does not appear to be a React issue. I can also confirm that onDataDisplayed() is not being called on the Android app (no toast is being shown).

I am currently testing the Android mirror application on a Xiaomi Redmi Note 3, running Android 6.0.1. I’m also using v0.1.5 of the Android Mirror SDK.

If this.mirrorContextManager.clearDisplayRequests(); should only be called once, what would be the most idiomatic approach for allowing users to change what data they send to the Mirror from the application? I want users to be able to change their name and route (particularly the route) in order to match what they wish to see.

I will continue to investigate why data is not being transferred over Bluetooth, but for now will be open to more suggestions.

Thanks again for all your help.

Please update Mirror SDK to 0.1.6. Android Studio should be complaining that there is a newer version.

Call display when you want to display something on the screen. If you call it more than once requests will be queued and delivered once connection is established. Each display request returns a handler that can be used to individually cancel request. So if user decided to change data before it was displayed then you need to cancel previous request.

DataHandle handle;
...
if (handle != null) {
      handle.cancel();
}
handle  = ctxMgr.display(dictionary, Zone.FAR, new DisplayCallback() { ... });

Remember to use destroy method on MirrorContextManager when destroying activity (no need to call clearDisplayRequests() then).

Could you send logcat output from your app? It would be useful for further investigation.

So I managed to sort things out. Turns out it was a permissions issue, more specifically, the application needed ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION permissions in order to work correctly. This wasn’t mentioned anywhere in the Mirror documentation, nor was it logged as an error in logcat, so I completely missed it. It would have been nice if this was alluded to in the docs. Either way though, everything has been sorted out.

Thanks for all of your help!