beagle

Project Url: pandulapeter/beagle
Introduction: A smart and reliable companion library for debugging your Android apps
More: Author   ReportBugs   
Tags:

A smart, reliable, and highly customizable debug menu library for Android apps that supports screen recording, network activity logging, and many other useful features.

WARNING! The library underwent a complete rewrite with version 2.0.0. Click here to see the readme file for the old version, or check out this migration guide if you're ready to upgrade.

See it in action

Clone this repository, pick a build variant and run the app configuration. It should look something like this:

This demo application also contains instructions on how to set up Beagle and how to implement the various features that are being showcased. You should definitely consider giving it a try if you're interested in using the library in your projects. If you don't feel like building it for yourself, you can also download it from the Play Store:

The tutorials in the app cover everything from this readme, but in more detail.

Use it in your project

Step 1: Add the Jitpack repository

Make sure that the following is part of your project-level build.gradle file:

allprojects {
    repositories {
        …
        maven { url "https://jitpack.io" }
    }
}

Step 2: Pick a UI implementation and configure the dependencies

The actual UI of the debug menu can be displayed in multiple ways, which is specified by the suffix of the dependency. The following versions exist:

  • ui-activity - Displays the debug menu as a new screen (not recommended: modals are more useful).
  • ui-bottom-sheet - Displays the debug menu as a modal bottom sheet (recommended).
  • ui-dialog - Displays the debug menu as a modal dialog (recommended).
  • ui-drawer - Displays the debug menu as a side navigation drawer (highly recommended).
  • ui-view - Displaying DebugMenuView is your responsibility (not recommended: shake to open, Beagle.show(), Beagle.hide(), the related VisibilityListener as well as the inset handling logic won't work out of the box).
  • noop - No UI, no logic. It has the same public API as all other variants, but it does nothing (this is intended for production builds).

So for example if you prefer the Drawer UI, something like the following needs to be added to your module-level build.gradle file (check the widget below the code snippet for the latest version):

dependencies {
    …
    def beagleVersion = "2.0.0-beta14"
    debugImplementation "com.github.pandulapeter.beagle:ui-drawer:$beagleVersion"
    releaseImplementation "com.github.pandulapeter.beagle:noop:$beagleVersion"
}

The latest version is:

Note: In case of the drawer UI, if you have overwritten the Activity's onBackPressed() method, you might notice that the default back navigation handling does not always work as expected. To fix this, in every Activity's onBackPressed() you should check that Beagle.hide() returns false before doing any other checks or calling the super implementation.

Step 3: Initialize the library

Just one line of code, preferably in the Application's onCreate() method:

Beagle.initialize(this)

Optionally you can add the following parameters to this function:

  • The appearance of the menu can be personalized by specifying an Appearance instance.
  • The behavior of the menu can be personalized by specifying a Behavior instance.

By default you can fetch Beagle by shaking the device. If nothing happens, make sure you're not adding any Fragments to the container with the ID android.R.id.content (if that's the case, introducing another FrameLayout into the View hierarchy for debug builds is a simple fix to the problem).

Step 4: Finish the setup by adding modules

After this a number of modules should be provided, but this configuration can be changed at any time (from any thread) and the UI will automatically be updated. The simplest way of doing this is by calling:

Beagle.set(module1, module2, …)

At this point you should be aware of two options:

  • The list of built-in modules. Every file in this package is documented. These modules should cover most use cases and have the advantage of also providing a fake, noop implementation which means that no part of their logic is compiled into your release builds.
  • The ability to write custom modules. For this a good starting point is looking at the built-in implementations from above, but this document also provides some guidance.

Check out the showcase app for some ideas on what is possible with the built-in modules or for an interactive tool which can be used to preview any module configuration and generate the code for it.

Here is a minimal example that should work for most projects:

Beagle.set(
    HeaderModule(
        title = getString(R.string.app_name),
        subtitle = BuildConfig.APPLICATION_ID,
        text = "${BuildConfig.BUILD_TYPE} v${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})"
    ),
    AppInfoButtonModule(),
    DeveloperOptionsButtonModule(),
    KeylineOverlaySwitchModule(),
    AnimationDurationSwitchModule(),
    DividerModule(),
    ScreenCaptureToolboxModule(),
    LogListModule(),
    DeviceInfoModule()
)

Logging and intercepting network events

To add content to LogListModule or NetworkLogListModule, you can simply call Beagle.log() and Beagle.logNetworkEvent() respectively. However, you might need to access this functionality from pure Java / Kotlin modules or, in the case of network events, you might want to use an Interceptor / Logger that works out of the box.

Logging

To access the same functionality that Beagle.log() provides from a pure Kotlin / Java module, first you need to add the following to the module in question:

dependencies {
    …
    api "com.github.pandulapeter.beagle:log:$beagleVersion"

    // Alternative for Android modules:
    // debugApi "com.github.pandulapeter.beagle:log:$beagleVersion"
    // releaseApi "com.github.pandulapeter.beagle:log-noop:$beagleVersion"
}

These libraries provide the BeagleLogger object which needs to be connected to the main library when it is initialized in the Application class:

Beagle.initialize(
    …
    behavior = Behavior(
        …
        logger = BeagleLogger
    )
)

To add log messages, now you can call the following:

BeagleLogger.log(…)

The messages list will be merged with the ones logged using the regular Beagle.log() function (unless they are filtered by their tags) and can be displayed using a LogListModule. You can also use BeagleLogger.clearLogs() if you cannot access Beagle.clearLogs().

Intercepting network events

Not bundling the network interceptor with the main library was mainly done to provide a pure Kotlin dependency that does not use the Android SDK, similarly to the logger solution described above. However, another reason was to provide the ability to choose between multiple implementations, in function of the project tech stack. At the moment, out of the box, Beagle can hook into two networking libraries (but manually calling Beagle.logNetworkEvent() is always an option).

OkHttp

Add the following to the module where your networking logic is implemented:

dependencies {
    …
    api "com.github.pandulapeter.beagle:log-okhttp:$beagleVersion"

    // Alternative for Android modules:
    // debugApi "com.github.pandulapeter.beagle:log-okhttp:$beagleVersion"
    // releaseApi "com.github.pandulapeter.beagle:log-okhttp-noop:$beagleVersion"
}

This will introduce the BeagleOkHttpLogger object which first needs to be connected to the main library, the moment it gets initialized:

Beagle.initialize(
    …
    behavior = Behavior(
        …
        networkLoggers = listOf(BeagleOkHttpLogger)
    )
)

The last step is setting up the Interceptor (the awkward casting is there to make sure the noop implementation does nothing while still having the same public API):

val client = OkHttpClient.Builder()
    …
    .apply { (BeagleOkHttpLogger.logger as? Interceptor?)?.let { addInterceptor(it) } }
    .build()

Ktor (Android engine)

Add the following to the module where your networking logic is implemented:

dependencies {
    …
    api "com.github.pandulapeter.beagle:log-ktor:$beagleVersion"

    // Alternative for Android modules:
    // debugApi "com.github.pandulapeter.beagle:log-ktor:$beagleVersion"
    // releaseApi "com.github.pandulapeter.beagle:log-ktor-noop:$beagleVersion"
}

This will introduce the BeagleKtorLogger object which first needs to be connected to the main library, the moment it gets initialized:

Beagle.initialize(
    …
    behavior = Behavior(
        …
        networkLoggers = listOf(BeagleKtorLogger)
    )
)

The last step is setting up the Logger (the awkward casting is there to make sure the noop implementation does nothing while still having the same public API):

val client = HttpClient(engine) {
    …
    (BeagleKtorLogger.logger as? HttpClientFeature<*,*>?)?.let { install(it) }
}

Documentation

All public functions are documented with KDoc. The BeagleContract file is a good start for learning about all the built-in capabilities. For information on the individual modules, see the relevant class headers.

If you're interested in what's under the hood, this document can be helpful while navigating the source code.

Changelog

Check out the Releases page for the changes in every version.

If you're updating from a version older than 2.x.x, check out this migration guide.

The library uses semantic versioning: MAJOR.MINOR.PATCH where PATCH changes only contain bug fixes, MINOR changes add new features and MAJOR changes introduce breaking modifications to the API.

Known issues

Check out the Issues page for the list of know problems.

Don't hesitate to open a new issue if you find a bug or if you have any questions / feature requests!

Buy me a beer

If you found my work useful and are considering a small donation, the About section of the the showcase app has an option for you to do so. Thanks in advance!

License

Copyright 2020 Pandula Péter

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Apps
About Me
GitHub: Trinea
Facebook: Dev Tools