Skip to content

Commit 2ec6f1f

Browse files
Merge pull request #153 from Countly/qt_app
feat: qt demo app w widgets
2 parents 644fcf7 + 348562d commit 2ec6f1f

5 files changed

Lines changed: 2819 additions & 0 deletions

File tree

examples/qt_demo/.gitignore

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Build directory
2+
build/
3+
4+
# CMake generated files
5+
CMakeCache.txt
6+
CMakeFiles/
7+
cmake_install.cmake
8+
Makefile
9+
10+
# Qt auto-generated
11+
*_autogen/
12+
moc_*
13+
ui_*
14+
qrc_*
15+
16+
# Compiled binaries
17+
*.o
18+
*.obj
19+
*.exe
20+
*.out
21+
*.app
22+
23+
# IDE files
24+
.vscode/
25+
.idea/
26+
*.user
27+
*.swp
28+
*~
29+
.DS_Store
30+
31+
# Dev credentials — do not commit
32+
dev_config.hpp

examples/qt_demo/CMakeLists.txt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
cmake_minimum_required(VERSION 3.16)
2+
project(qt_demo)
3+
4+
set(CMAKE_CXX_STANDARD 17)
5+
set(CMAKE_AUTOMOC ON)
6+
7+
find_package(CURL REQUIRED)
8+
find_package(Qt6 REQUIRED COMPONENTS Widgets WebEngineWidgets)
9+
10+
# Countly SDK paths — defaults to the parent SDK checkout (this demo lives in
11+
# countly-sdk-cpp/examples/qt_demo/). Override with -DCOUNTLY_SDK_DIR=... if
12+
# you keep the SDK somewhere else.
13+
if(NOT COUNTLY_SDK_DIR)
14+
get_filename_component(COUNTLY_SDK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../.." ABSOLUTE)
15+
endif()
16+
set(COUNTLY_BUILD_DIR "${COUNTLY_SDK_DIR}/build")
17+
18+
add_executable(qt_demo main.cpp)
19+
target_compile_definitions(qt_demo PRIVATE COUNTLY_USE_SQLITE)
20+
target_include_directories(qt_demo PRIVATE
21+
${COUNTLY_SDK_DIR}/include
22+
${COUNTLY_SDK_DIR}/vendor/json/include
23+
)
24+
target_link_libraries(qt_demo PRIVATE
25+
CURL::libcurl
26+
Qt6::Widgets
27+
Qt6::WebEngineWidgets
28+
${COUNTLY_BUILD_DIR}/libcountly.dylib
29+
)

examples/qt_demo/Countly_Feedback_Widget_Implementation_Guide.html

Lines changed: 512 additions & 0 deletions
Large diffs are not rendered by default.

examples/qt_demo/README.md

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
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

Comments
 (0)