|
| 1 | +# Countly C++ SDK Demo App |
| 2 | + |
| 3 | +A Qt6 desktop application for manually testing all features of the Countly C++ SDK, including SDK Behavior Settings (SBS) and a standalone Feedback Widgets flow. |
| 4 | + |
| 5 | +## Prerequisites |
| 6 | + |
| 7 | +| Requirement | Notes | |
| 8 | +| ----------- | ----- | |
| 9 | +| **CMake** | 3.16 or newer | |
| 10 | +| **A C++17 compiler** | AppleClang / Clang / GCC are all fine | |
| 11 | +| **Qt 6** | `Widgets` + `WebEngineWidgets` modules are both required | |
| 12 | +| **libcurl** | Used by the Feedback Widgets tab for direct HTTP calls | |
| 13 | +| **Countly C++ SDK** | Built locally with `-DCOUNTLY_USE_SQLITE=ON` | |
| 14 | + |
| 15 | +### Installing the dependencies on macOS |
| 16 | + |
| 17 | +Homebrew's Qt ships as ~40 keg-only sub-packages (`qtbase`, `qtwebengine`, ...). Qt's own CMake config expects them in a single prefix, so install the unified `qt` formula — it symlinks all the sub-kegs into `/opt/homebrew/opt/qt` where `find_package(Qt6)` can discover every component: |
| 18 | + |
| 19 | +```bash |
| 20 | +brew install cmake qt curl |
| 21 | +``` |
| 22 | + |
| 23 | +`brew install qt@6` or installing `qtbase`/`qtwebengine` individually is **not enough** — `find_package(Qt6 REQUIRED COMPONENTS Widgets WebEngineWidgets)` will fail because the per-keg prefixes don't contain each other's config files. If you already have the sub-packages installed, running `brew install qt` is quick; it only adds the symlink farm on top. |
| 24 | + |
| 25 | +### Installing the dependencies on Linux |
| 26 | + |
| 27 | +On Debian/Ubuntu: |
| 28 | + |
| 29 | +```bash |
| 30 | +sudo apt install cmake build-essential libcurl4-openssl-dev \ |
| 31 | + qt6-base-dev qt6-webengine-dev |
| 32 | +``` |
| 33 | + |
| 34 | +On Fedora/RHEL: |
| 35 | + |
| 36 | +```bash |
| 37 | +sudo dnf install cmake gcc-c++ libcurl-devel \ |
| 38 | + qt6-qtbase-devel qt6-qtwebengine-devel |
| 39 | +``` |
| 40 | + |
| 41 | +## Setup |
| 42 | + |
| 43 | +### 1. Build the Countly C++ SDK |
| 44 | + |
| 45 | +This demo lives inside the SDK repo at `examples/qt_demo/`. It links against `libcountly.dylib` (macOS) / `libcountly.so` (Linux) produced by the SDK's own build, so build the SDK first with SQLite support: |
| 46 | + |
| 47 | +```bash |
| 48 | +cd /path/to/countly-sdk-cpp # the repo root, two levels above this README |
| 49 | +mkdir -p build && cd build |
| 50 | +cmake .. -DCOUNTLY_USE_SQLITE=ON |
| 51 | +make |
| 52 | +``` |
| 53 | + |
| 54 | +After this, `countly-sdk-cpp/build/` will contain the Countly shared library. The demo's `CMakeLists.txt` discovers the SDK automatically via `${CMAKE_CURRENT_SOURCE_DIR}/../..` — no path flag needed. |
| 55 | + |
| 56 | +### 2. Create `dev_config.hpp` |
| 57 | + |
| 58 | +The "Load Dev Config" button in the Init tab pulls credentials from this header so you don't have to retype them every run. It's in `.gitignore` — never commit real credentials. |
| 59 | + |
| 60 | +Create `examples/qt_demo/dev_config.hpp` with: |
| 61 | + |
| 62 | +```cpp |
| 63 | +#ifndef DEV_CONFIG_HPP |
| 64 | +#define DEV_CONFIG_HPP |
| 65 | + |
| 66 | +#include <string> |
| 67 | + |
| 68 | +struct DevConfig { |
| 69 | + std::string serverUrl = "https://your.server.ly"; |
| 70 | + std::string appKey = "YOUR_APP_KEY"; |
| 71 | + std::string deviceId = "test-device-id"; |
| 72 | + std::string dbPath = "countly_demo.db"; |
| 73 | + std::string port = ""; |
| 74 | + std::string salt = ""; |
| 75 | + std::string eqThreshold = ""; |
| 76 | + std::string rqMaxSize = ""; |
| 77 | + std::string rqBatchSize = ""; |
| 78 | + std::string sessionInterval = ""; |
| 79 | + std::string updateInterval = ""; |
| 80 | + std::string metricsOs = "macOS"; |
| 81 | + std::string metricsOsVersion = "15.0"; |
| 82 | + std::string metricsDevice = "MacBook"; |
| 83 | + std::string metricsResolution = "2560x1600"; |
| 84 | + std::string metricsCarrier = ""; |
| 85 | + std::string metricsAppVersion = "1.0"; |
| 86 | + std::string sbsJson = ""; |
| 87 | + bool manualSession = false; |
| 88 | + bool disableSBSUpdates = false; |
| 89 | + bool alwaysPost = false; |
| 90 | + bool enableRemoteConfig = false; |
| 91 | + bool disableAutoEventsOnUP = false; |
| 92 | +}; |
| 93 | + |
| 94 | +#endif |
| 95 | +``` |
| 96 | + |
| 97 | +### 3. Configure and build the demo |
| 98 | + |
| 99 | +From the `examples/qt_demo/` folder: |
| 100 | + |
| 101 | +```bash |
| 102 | +mkdir -p build && cd build |
| 103 | + |
| 104 | +cmake \ |
| 105 | + -DCMAKE_PREFIX_PATH=/opt/homebrew/opt/qt \ |
| 106 | + .. |
| 107 | + |
| 108 | +make -j$(sysctl -n hw.ncpu) # on Linux: -j$(nproc) |
| 109 | +``` |
| 110 | + |
| 111 | +One flag worth explaining: |
| 112 | + |
| 113 | +- **`CMAKE_PREFIX_PATH`** tells CMake where Qt lives. On macOS with Homebrew this is `/opt/homebrew/opt/qt` (Apple Silicon) or `/usr/local/opt/qt` (Intel). On Linux with distro packages you can usually omit it entirely — the pkg-config files are already on the default search path. |
| 114 | + |
| 115 | +If you keep the SDK checkout somewhere other than this demo's grandparent directory (e.g. a separate working tree), pass `-DCOUNTLY_SDK_DIR=/path/to/countly-sdk-cpp` to override the default. |
| 116 | + |
| 117 | +The binary is written to `build/qt_demo`. |
| 118 | + |
| 119 | +### 4. Run |
| 120 | + |
| 121 | +```bash |
| 122 | +./qt_demo |
| 123 | +``` |
| 124 | + |
| 125 | +In the app: |
| 126 | + |
| 127 | +1. Go to **Init / Config**, click **Load Dev Config** (or fill the fields manually), then **Initialize SDK**. |
| 128 | +2. Switch tabs to exercise the feature you want to test. The SDK's log output streams into the panel at the bottom in real time. |
| 129 | +3. For feedback widgets, head to the **Feedback Widgets** tab and click **Fetch Widgets** — it reuses the Init tab's server URL / app key / device ID. Click any widget card to present it in the embedded web view. |
| 130 | + |
| 131 | +## Tabs |
| 132 | + |
| 133 | +The app has 10 tabs covering all SDK features: |
| 134 | + |
| 135 | +| Tab | Description | |
| 136 | +| --- | ----------- | |
| 137 | +| **Init / Config** | Server URL, app key, device ID, metrics, SDK options (manual sessions, SBS JSON, salt, etc.). Initialize and stop the SDK from here. | |
| 138 | +| **Sessions** | Begin, update, and end sessions manually. | |
| 139 | +| **Events** | Send basic events, events with count/sum/duration, and events with custom segmentation. | |
| 140 | +| **Views** | Start and stop named views. | |
| 141 | +| **Crashes** | Record handled exceptions, add breadcrumbs, set custom crash segments. | |
| 142 | +| **User Profile** | Set standard user properties (name, email, etc.) and custom key-value pairs. | |
| 143 | +| **Location** | Set country code, city, GPS coordinates, or IP address. Disable location. | |
| 144 | +| **Device ID** | Change device ID with or without server merge. | |
| 145 | +| **Remote Config** | Fetch all remote config values or fetch specific keys. View the returned JSON. | |
| 146 | +| **Feedback Widgets** | Standalone HTTP flow (does not use the SDK). Fetches feedback widgets from the server and renders them in an embedded `QWebEngineView`, intercepting Countly widget communication URLs. Uses the Server URL / App Key / Device ID from the Init tab. See `Countly_Feedback_Widget_Implementation_Guide.html` for the underlying protocol. | |
| 147 | + |
| 148 | +## Log Panel |
| 149 | + |
| 150 | +A live log panel at the bottom of the window shows all SDK log output in real time. Logs are delivered via a thread-safe Qt signal/slot connection so background SDK threads can log without crashing the UI. |
| 151 | + |
| 152 | +## Troubleshooting |
| 153 | + |
| 154 | +- **`Could NOT find Qt6WebEngineWidgets (missing: Qt6WebEngineWidgets_DIR)`** |
| 155 | + You're pointing CMake at a Qt prefix that only contains `qtbase`. Install the unified Homebrew `qt` formula (`brew install qt`) or add the `qtwebengine` prefix alongside in `CMAKE_PREFIX_PATH`. |
| 156 | + |
| 157 | +- **Build fails after editing `CMakeLists.txt` or moving the SDK** |
| 158 | + Delete the `build/` folder and reconfigure. CMake caches `find_package` results; adding a new dependency or changing paths requires a fresh configure, not an incremental rebuild. |
| 159 | + |
| 160 | +- **`libcountly.dylib` not found at launch** |
| 161 | + The build embeds `${COUNTLY_SDK_DIR}/build` as an `LC_RPATH`. Moving the SDK directory after building invalidates that path. Reconfigure (or patch with `install_name_tool -add_rpath`). |
| 162 | + |
| 163 | +- **WebEngine window is blank / never loads** |
| 164 | + On macOS the WebEngine process needs network permissions; make sure your firewall isn't blocking `QtWebEngineProcess`. You can also watch the log panel — the demo logs every navigation interception the page makes. |
| 165 | + |
| 166 | +## Notes |
| 167 | + |
| 168 | +- `dev_config.hpp` is in `.gitignore` — never commit credentials. |
| 169 | +- The SDK is linked as a dynamic library. Rebuild the SDK first whenever you update it, then rebuild the demo. |
| 170 | +- The Feedback Widgets tab intentionally bypasses the C++ SDK — it talks to `/o/sdk?method=feedback` and `/feedback/<type>` directly via libcurl. This mirrors the manual protocol documented in `Countly_Feedback_Widget_Implementation_Guide.html` and is useful for validating the server-side widget setup independently of the SDK. |
| 171 | +- On exit the app calls `Countly::getInstance().stop()` before the main window is destroyed so background SDK threads don't call back into a dead GUI. |
0 commit comments