📜 ⬆️ ⬇️

AOP or how to write your bike for analytics

image
In large projects, when implementing the event tracking logic, one often faces the problem of code contamination with calls to tracking methods, the inconvenience of explicitly associating objects with events, and supporting these events when models or ui behavior change.

Due to the reasons described above, it occurred to me to write my own solution, which, of course, will not go further than my git repository and this article.

Who is not afraid of reflection and slow code - please under the cat.

Maybe not needed?
')
In product applications, how many times they tap on this button or how low the list is scrolled depends on the development of the application as a product, its external interface and functionality.
Because of this, the code begins to cut method calls for tracking events that are scattered throughout the view and still take into account the state of certain objects.
I want to make the addition of new events less painful and solve several problems taken from the air with which I had to face.

I want to achieve the following:
1) The minimum amount of code for a new event;
2) The minimum amount of code in the view;
3) Convenient system of binding objects to events.

The decision is based on annotations, reflections and aspects.
To implement the aspect of our application, I will use AspectJ. It is an aspect-oriented extension for the Java language. At the moment this is probably the most popular AOP engine.
By the way, this engine was developed by the very people who proposed the paradigm of aspects.

How it works
To intercept a call of methods necessary to us we create the class marked as @Aspect .
We create a connection point with our methods and create a method marked @Around that will be executed on the connection point. AspectJ is functionally rich and supports a large number of options for slicing points and tips, but this is not about that now.

@Aspect public class ViewEventsInjector { private static final String POINTCUT_METHOD = "execution(@com.makarov.ui.tracker.library.annotations.ViewEvent * *(..))"; @Pointcut(POINTCUT_METHOD) public void methodAnnotatedWithViewEvent() { } @Around("methodAnnotatedWithViewEvent()") public Object joinPoint(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature ms = (MethodSignature) joinPoint.getSignature(); Method method = ms.getMethod(); Object object = joinPoint.getThis(); Object[] arg = joinPoint.getArgs(); /*  ,                */ Object result = joinPoint.proceed(); return result; } } 


Implementation

Abstract for observable view
 @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD }) public @interface LoggerView { String value(); } 

The annotation parameter is the name of the view element for more convenient reading of events / logs.

As a result, after initialization, we have a Map in which the id view of the elements tracked by us lie.

Interception of events falls entirely on the shoulders of aspects.
We will focus on annotations, which we have marked the methods of view events.

The logic is as follows:
1) Intercept the method call;
2) Find its handler, which we added to the map with all possible method handlers;
3) Find by the parameters of the annotation all the objects that need to be traced;
4) Create an Event object from our received data;
4) Save the event.

Annotation for the methods on which our events will be hung:

 @Retention(RetentionPolicy.CLASS) @Target({ ElementType.CONSTRUCTOR, ElementType.METHOD }) public @interface ViewEvent { String[] value(); } 

To unify the models that we want to bind to our events, we introduce the interface that the model must implement:

 public interface LoggingModel { Map<String, String> getModelLogState(); } 

Interface implementation example:

 public class Artist implements LoggingModel { private final String mId; private final String mName; public Artist(String id, String name){ mId = id; mName = name; } /* ... */ @Override public Map<String, String> getModelLogState() { Map<String, String> logMap = new HashMap<>(); logMap.put("artistId", mId); logMap.put("artistName", mName); return logMap; } } 


Putting it all together

Well, finally we collect all this and in a few annotations we begin to track the events we need.

 public class MainActivity extends AppCompatActivity implements View.OnClickListener, TextWatcher{ public static final String TAG = MainActivity.class.getSimpleName(); @LoggerView("first button") public Button button; public Button button2; @LoggerView("test editText") public EditText editText; public Artist artist = new Artist("123", "qwe"); public Track track = new Track("ABS"); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); /*  view  */ ViewEventsInjector.init(); ViewEventsInjector.inject(this); } @Override @AttachState({"artist","track"}) @ViewEvent(ViewEventsTracker.CLICK) public void onClick(View v) { Log.d(TAG, "method onClick - " + v.getId()); } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override @AttachState({"artist"}) @ViewEvent(ViewEventsTracker.AFTER_TEXT_CHANGED) public void afterTextChanged(Editable s) { Log.d(TAG, "afterTextChanged"); } } 

Run the project, try tapping the button, then enter something in the text field.

And we see our long-awaited logs, without a single line of logic in the presentation.

 07-13 13:52:16.406 D/SimpleRepository﹕ Event{nameView='fist button', nameEvent='onClick', mModelList=[Artist@52a30ec8, Track@52a31040], methodParameters = null, mDate = Mon Jul 13 13:52:16 EDT 2015} 07-13 13:52:24.254 D/SimpleRepository﹕ Event{nameView='textView', nameEvent='afterTextChanged', mModelList=[Artist@52a30ec8], methodParameters= {text = hello}, mDate=Mon Jul 13 13:52:24 EDT 2015} 

In my opinion, we even solved a few problems with this simple project engineer and perhaps saved some amount of time for routine activities.
If you spend some more time, you could optimize the logic of the aspect quite well, for example, slightly alter the storage of objects so as not to receive them every time through reflection.

If someone suddenly decides to take it and bring this thing to mind, you are welcome here .

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


All Articles