📜 ⬆️ ⬇️

Qt signal / slot implementation on Android

Foreword


Recently I have ported a rather large project from Qt (C ++) to Android (Java), in the process of work I often had to apply dynamic linking of objects. The trouble was that, in contrast to the usual signals and slots, Qt in Java is implemented through listeners, and how much I did not try to convince myself that this method is equivalent and also takes place to be the same convenience as when using signals and slots could not be reached.
For example, we need to associate a slider (QSlider in Qt or SeekBar in Android) with some action, at least to bind another slider that will obediently move after the first one. In Qt, a similar operation looks like this:


Example 1

//   QSlider *primary = new QSlider(this); QSlider *secondary = new QSlider(this); //   ,  // ... //      connect(primary, SIGNAL(valueChanged(int)), secondary, SLOT(setValue(int))); 


As a result, we get the connection of the valueChanged () signal of the primary slider with the setValue () slot of the secondary slider . The same on Android:
')
Example 2

 //   SeekBar firstBar = (SeekBar)findViewById(R.id.firstSeekBar); SeekBar secondBar = (SeekBar)findViewById(R.id.secondSeekBar); //  // ... //      firstBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override // ........ @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { secondBar.setProgress(progress); } }); 


And as a result we get the same thing, that is, we associate the displacement of firstBar and secondBar .
Let's see what's going on here. Somewhere in the depths of firstBar there is a variable of the OnSeekBarChangeListener type which, when a move occurs, is checked for null and if it turns out to be non-zero, and so it happens, its onProgressChanged () method will be called with the appropriate parameters which in turn will call secondBar.setProgress (progress) and set the value of the second slider.
Everything is very clear and understandable, although somewhat cumbersome. Qt is more concise in this case, although to implement dynamic binding it goes beyond C ++ to generate code in the project building process using MOC (Meta Object Compiler) . You have to pay for conciseness, and it becomes obvious when in the process of debugging you get into the generation code. But fortunately, if you follow the simplest rules, this is extremely rare.
But back to Android. All Android API classes have a sufficient set of liseners to provide convenience of their use, but what to do if there is a large array of code operating with signals and slots? Googling, I found several implementations of signals and slots in Java, the most worthy of which, not surprisingly, in the Qt Jambi library, an unfairly forgotten implementation of Qt in Java. An excellent implementation, however, did not suit me for several reasons, the most weighty of which, the inconsistency of the syntax with the original, it is strange that the same technology as part of the libraries for C ++ and Java is implemented so differently.
As a result, the idea to implement the signals and slots for Android on Java independently emerged.

Task


Implement a Java signal and slot engine as close as possible to the Qt C ++ syntax using the Android API.

Implementation


What happened

After several attempts, the Java Connector class was written on less than 600 lines with several static methods and a static map (Map) of signals and slots, which is essentially a singleton. All functionality is enclosed in four static methods:

  1. boolean connect (Object sender, String signal, Object receiver, String slot, ConnectionType type)
  2. void disconnect (Object sender, String signal, Object receiver, String slot)
  3. void emit (Object sender, String signalName, Object ... params)
  4. Object sender ()


In the runner example, the connection would look like this:

Example 3

 //     Android API    private static class SeekBarChangeListener implements SeekBar.OnSeekBarChangeListener { private Object mSender = null; public SeekBarChangeListener(Object sender) { mSender = sender; } @Override // ........ @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { Connector.emit(mSender, "progressChanged", progress); } } //   SeekBar firstBar = (SeekBar)findViewById(R.id.firstSeekBar); SeekBar secondBar = (SeekBar)findViewById(R.id.secondSeekBar); //  // ... //  firsBar   firstBar.setOnSeekBarChangeListener(new SeekBarChangeListener(firstBar)); //      Connector.connect(firstBar, "SIGNAL(progressChanged(int))" , secondBar, "SLOT(setProgress(int))"); 

At first glance it may seem that the implementation with the Connector is more cumbersome than with the help of the logger, but do not forget that the SeekBar class is sharpened for the use of the logger, like all other standard Android API classes, because you have to use wrappers. Much more benefits can be obtained by using a connector when developing your classes, or by porting Qt projects on Android:
  1. no need to create an interface for licenser
  2. no need to create a variable for it
  3. no setter method needed for licer


For more complex examples, details about the implementation details, see the next article.
(To be continued)

Source: https://habr.com/ru/post/157363/


All Articles