Initial commit 🎉
fbshipit-source-id: b6fc29740c6875d2e78953b8a7123890a67930f2 Co-authored-by: Sebastian McKenzie <sebmck@fb.com> Co-authored-by: John Knox <jknox@fb.com> Co-authored-by: Emil Sjölander <emilsj@fb.com> Co-authored-by: Pritesh Nandgaonkar <prit91@fb.com>
This commit is contained in:
BIN
docs/assets/initial.png
Normal file
BIN
docs/assets/initial.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 206 KiB |
BIN
docs/assets/layout.png
Normal file
BIN
docs/assets/layout.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 524 KiB |
BIN
docs/assets/logs.png
Normal file
BIN
docs/assets/logs.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 298 KiB |
BIN
docs/assets/network.png
Normal file
BIN
docs/assets/network.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 616 KiB |
46
docs/communitcating.md
Normal file
46
docs/communitcating.md
Normal file
@@ -0,0 +1,46 @@
|
||||
---
|
||||
id: communicating
|
||||
title: Device Communication
|
||||
sidebar_label: Device Communication
|
||||
---
|
||||
|
||||
To start communicating with a client your plugin must implement the init function. Once this function has been called the active client can also be accessed via `this.client`. This `id` of the plugin in JavaScript much match the native plugin `id` to allow for them to communicate.
|
||||
|
||||
```javascript
|
||||
class extends SonarPlugin {
|
||||
static title = "MyPlugin";
|
||||
static id = 'MyPlugin';
|
||||
|
||||
init() {
|
||||
// Setup subscriptions etc using this.client
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There are three main ways your desktop plugin can communicate with connected devices.
|
||||
|
||||
## Remote method calls
|
||||
|
||||
With remote method calls your plugin can call a method on the attached client. This is useful for querying information from the client that your plugin wants to display. The first parameter is the name of the client API and the second is the specific method on that API. Optionally a JSON object can be passed as an argument to the client.
|
||||
|
||||
```javascript
|
||||
this.client.call('methodName', DATA).then(res => {
|
||||
// res contains client response
|
||||
});
|
||||
```
|
||||
|
||||
This function return as promise so that you can await a potential response from the client. If you are calling a method on the client but don't expect a response then you should instead opt for using the `send()` function.
|
||||
|
||||
```javascript
|
||||
this.client.send('methodName', DATA);
|
||||
```
|
||||
|
||||
## Subscriptions
|
||||
|
||||
A client is not only able to respond to method calls but also push data directly to the Sonar desktop app. With the subscribe API your plugin can subscribe to there pushes from the client. Pass the name of the method and the API it is part of as well as a callback function to start a subscription. Any time the client sends a push matching this method the callback will be called with any attached data as a javascript object.
|
||||
|
||||
```javascript
|
||||
this.client.subscribe('methodName', data => {
|
||||
// data contains any payload sent by the client
|
||||
});
|
||||
```
|
||||
140
docs/create-plugin.md
Normal file
140
docs/create-plugin.md
Normal file
@@ -0,0 +1,140 @@
|
||||
---
|
||||
id: create-plugin
|
||||
title: Mobile Setup
|
||||
sidebar_label: Mobile Setup
|
||||
---
|
||||
|
||||
## Implement SonarPlugin
|
||||
|
||||
Create a class implementing `SonarPlugin`.
|
||||
|
||||
### Android
|
||||
|
||||
```java
|
||||
public class MySonarPlugin implements SonarPlugin {
|
||||
private SonarConnection mConnection;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "MySonarPlugin";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnect(SonarConnection connection) throws Exception {
|
||||
mConnection = connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnect() throws Exception {
|
||||
mConnection = null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### iOS
|
||||
|
||||
```objective-c
|
||||
@interface MySonarPlugin : NSObject<SonarPlugin>
|
||||
@end
|
||||
|
||||
@implementation MySonarPlugin
|
||||
|
||||
- (NSString*)identifier { return @"MySonarPlugin"; }
|
||||
- (void)didConnect:(SonarConnection*)connection {}
|
||||
- (void)didDisonnect {}
|
||||
|
||||
@end
|
||||
```
|
||||
|
||||
### C++
|
||||
|
||||
```c++
|
||||
class MySonarPlugin : public SonarPlugin {
|
||||
public:
|
||||
std::string identifier() const override { return "MySonarPlugin"; }
|
||||
void didConnect(std::shared_ptr<SonarConnection> conn) override;
|
||||
void didDisconnect() override;
|
||||
};
|
||||
```
|
||||
|
||||
## Using SonarConnection
|
||||
|
||||
Using the `SonarConnection` object you can register a receiver of a desktop method call and respond with data.
|
||||
|
||||
### Android
|
||||
|
||||
```java
|
||||
connection.receive("getData", new SonarReceiver() {
|
||||
@Override
|
||||
public void onReceive(SonarObject params, SonarResponder responder) throws Exception {
|
||||
responder.success(
|
||||
new SonarObject.Builder()
|
||||
.put("data", MyData.get())
|
||||
.build());
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### iOS
|
||||
|
||||
```objective-c
|
||||
@interface MySonarPlugin : NSObject<SonarPlugin>
|
||||
@end
|
||||
|
||||
@implementation MySonarPlugin
|
||||
|
||||
- (NSString*)identifier { return @"MySonarPlugin"; }
|
||||
|
||||
- (void)didConnect:(SonarConnection*)connection
|
||||
{
|
||||
[connection receive:@"getData" withBlock:^(NSDictionary *params, SonarResponder *responder) {
|
||||
[responder success:@{
|
||||
@"data":[MyData get],
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)didDisonnect {}
|
||||
|
||||
@end
|
||||
```
|
||||
|
||||
### C++
|
||||
|
||||
```c++
|
||||
void MySonarPlugin::didConnect(std::shared_ptr<SonarConnection> conn) {
|
||||
conn->receive("getData", [](const folly::dynamic ¶ms,
|
||||
std::unique_ptr<SonarResponder> responder) {
|
||||
dynamic response = folly::dynamic::object("data", getMyData());
|
||||
responder->success(response);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Push data to the desktop
|
||||
|
||||
You don't have to wait for the desktop to request data though, you can also push data directly to the desktop.
|
||||
|
||||
### Android
|
||||
|
||||
```java
|
||||
connection.send("MyMessage",
|
||||
new SonarObject.Builder()
|
||||
.put("message", "Hello")
|
||||
.build()
|
||||
```
|
||||
|
||||
### iOS
|
||||
|
||||
```objective-c
|
||||
[connection send:@"getData" withParams:@{@"message":@"hello"}];
|
||||
```
|
||||
|
||||
### C++
|
||||
|
||||
```c++
|
||||
void MySonarPlugin::didConnect(std::shared_ptr<SonarConnection> conn) {
|
||||
dynamic message = folly::dynamic::object("message", "hello");
|
||||
conn->send("getData", message);
|
||||
}
|
||||
```
|
||||
86
docs/create-table-plugin.md
Normal file
86
docs/create-table-plugin.md
Normal file
@@ -0,0 +1,86 @@
|
||||
---
|
||||
id: create-table-plugin
|
||||
title: Create Table Plugin
|
||||
sidebar_label: Create Table Plugin
|
||||
---
|
||||
|
||||
A very common kind of Sonar plugin is a plugin which fetches some structured data from the device and presents it in a table.
|
||||
|
||||
To make building these kinds of plugins as easy as possible we have created an abstraction we call `createTablePlugin`. This is a function which manages the complexities of building a table plugin but still allows you to customize many things to suite your needs.
|
||||
|
||||
Below is a sample implementation of a desktop plugin based on `createTablePlugin`. It subscribes to updates from a client plugin with id `myplugin` sending rows to with the `newRow` method. A row can have any structure you want as long as it has a unique field `id` of type `string`.
|
||||
|
||||
See "[Create Plugin](create-plugin.md)" for how to create the native counterpart for your plugin.
|
||||
|
||||
```javascript
|
||||
import {ManagedDataInspector, Panel, Text, createTablePlugin} from 'sonar';
|
||||
|
||||
type Id = string;
|
||||
|
||||
type Row = {
|
||||
id: Id,
|
||||
column1: string,
|
||||
column2: string,
|
||||
column3: string,
|
||||
extras: Object,
|
||||
};
|
||||
|
||||
function buildRow(row: Row) {
|
||||
return {
|
||||
columns: {
|
||||
column1: {
|
||||
value: <Text>{row.column1}</Text>,
|
||||
filterValue: row.column1,
|
||||
},
|
||||
column2: {
|
||||
value: <Text>{row.column2}</Text>,
|
||||
filterValue: row.column2,
|
||||
},
|
||||
column3: {
|
||||
value: <Text>{row.column3}</Text>,
|
||||
filterValue: row.column3,
|
||||
},
|
||||
},
|
||||
key: row.id,
|
||||
copyText: JSON.stringify(row),
|
||||
filterValue: `${row.column1} ${row.column2} ${row.column3}`,
|
||||
};
|
||||
}
|
||||
|
||||
function renderSidebar(row: Row) {
|
||||
return (
|
||||
<Panel floating={false} heading={'Extras'}>
|
||||
<ManagedDataInspector data={JSON.parse(row.extras)} expandRoot={true} />
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
const columns = {
|
||||
time: {
|
||||
value: 'Column1',
|
||||
},
|
||||
module: {
|
||||
value: 'Column2',
|
||||
},
|
||||
name: {
|
||||
value: 'Column3',
|
||||
},
|
||||
};
|
||||
|
||||
const columnSizes = {
|
||||
time: '15%',
|
||||
module: '20%',
|
||||
name: 'flex',
|
||||
};
|
||||
|
||||
export default createTablePlugin({
|
||||
title: 'My Plugin', // Title of plugin
|
||||
id: 'myplugin', // ID of plugin
|
||||
method: 'newRow', // Method which should be subscribed to to get new rows with share Row (from above),
|
||||
icon: 'washing-machine',
|
||||
columns,
|
||||
columnSizes,
|
||||
renderSidebar,
|
||||
buildRow,
|
||||
});
|
||||
```
|
||||
32
docs/error-handling.md
Normal file
32
docs/error-handling.md
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
id: error-handling
|
||||
title: Error Handling
|
||||
sidebar_label: Error Handling
|
||||
---
|
||||
|
||||
Errors in Sonar plugins should be hidden from the user while providing actionable data to the plugin developer.
|
||||
|
||||
## Android
|
||||
|
||||
To gracefully handle errors in Sonar we provide the `ErrorReportingRunnable` class. This is a custom runnable which catches all exceptions, stopping them from crashing the application and reporting them to the plugin developer.
|
||||
|
||||
```java
|
||||
new ErrorReportingRunnable(mConnection) {
|
||||
@Override
|
||||
public void runOrThrow() throws Exception {
|
||||
mightThrowException();
|
||||
}
|
||||
}.run();
|
||||
```
|
||||
|
||||
Executing this block of code will always finish without error but may transfer any silences error to the Sonar desktop app. During plugin development these java stack traces are surfaced in the chrome dev console. In production the errors are instead sent to and a task is assigned so that you can quickly deploy a fix.
|
||||
|
||||
Always use `ErrorReportingRunnable` for error handling instead of `try`/`catch` or even worse letting errors crash the app. With ErrorReportingRunnable you won't block anyone and you won't hide any stack traces.
|
||||
|
||||
## C++
|
||||
|
||||
To gracefully handle errors in Sonar we perform all transactions inside of a try block which catches all exceptions, stopping them from crashing the application and reporting them to the plugin developer. This includes your own customs implementations of `SonarPlugin::didConnect()` and `SonarConnection::send()` and `::receive()`!
|
||||
|
||||
That means you can safely throw exceptions in your plugin code. The exception messages will be sent to the Sonar desktop app. During plugin development the exception messages are surfaced in the Chrome dev console.
|
||||
|
||||
If your plugin performs asynchronous work in which exceptions are thrown, these exceptions will not be caught by the Sonar infrastructure. You should handle them appropriately.
|
||||
82
docs/getting-started.md
Normal file
82
docs/getting-started.md
Normal file
@@ -0,0 +1,82 @@
|
||||
---
|
||||
id: getting-started
|
||||
title: Getting Started
|
||||
sidebar_label: Getting Started
|
||||
---
|
||||
|
||||
Sonar helps you debug Android and iOS apps running in an emulator/simulator or connected physical development devices. Sonar consists of two parts:
|
||||
|
||||
* The desktop app for macOS
|
||||
* The native mobile SDKs for Android and iOS
|
||||
|
||||
To use Sonar, you need to add the mobile SDK to your app.
|
||||
|
||||
## Setup
|
||||
|
||||
### Desktop app
|
||||
|
||||
The desktop part of Sonar doesn't need any particular setup. Simply [download the latest build](https://www.facebook.com/sonar/public/mac) of our app and launch it. The desktop app is available for macOS and requires a working installation of the Android/iOS development tools on your system.
|
||||
|
||||
Once you start Sonar and launch an emulator/simulator or connect a device, you will already be able to see the device logs in Sonar. To see app specific data, you need to integrate our native SDKs with your app.
|
||||
|
||||

|
||||
|
||||
### Setup your Android app
|
||||
|
||||
TODO: Install dependencies
|
||||
|
||||
TODO: Add dependencies to your `build.gradle` file.
|
||||
|
||||
Now you can initialize Sonar in your Application's `onCreate`-method like this:
|
||||
|
||||
```java
|
||||
public class MyApplication extends Application {
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
if (BuildConfig.DEBUG && SonarUtils.isMainProcess(mApplicationContext)) {
|
||||
final SonarClient client = AndroidSonarClient.getInstance(this);
|
||||
client.addPlugin(new MySonarPlugin());
|
||||
client.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Setup your iOS app
|
||||
|
||||
To integrate with our iOS app, you can use [CocoaPods](https://cocoapods.org). Add the mobile Sonar SDK to your `Podfile`:
|
||||
|
||||
```ruby
|
||||
platform :ios, '8.0'
|
||||
use_frameworks!
|
||||
|
||||
target 'MyApp' do
|
||||
pod 'Sonar', '~> 1.0'
|
||||
end
|
||||
```
|
||||
|
||||
and install the dependencies by running `pod install`. When you open the Xcode workspace file for your app, you now can import and initialize Sonar in your AppDelegate.
|
||||
|
||||
```objective-c
|
||||
#import <Sonar/SonarClient.h>
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
#if DEBUG
|
||||
SonarClient *client = [SonarClient sharedClient];
|
||||
[client addPlugin:[MySonarPlugin new]];
|
||||
[client start];
|
||||
#endif
|
||||
...
|
||||
}
|
||||
@end
|
||||
```
|
||||
|
||||
## Ready for takeoff
|
||||
|
||||
Finally you need to add plugins to your Sonar client. See [Network Plugin](network-plugin.md) and [Layout Inspector Plugin](layout-plugin.md) on how to add them.
|
||||
58
docs/jssetup.md
Normal file
58
docs/jssetup.md
Normal file
@@ -0,0 +1,58 @@
|
||||
---
|
||||
id: js-setup
|
||||
title: JavaScript Setup
|
||||
sidebar_label: JavaScript Setup
|
||||
---
|
||||
|
||||
## Creating the plugin UI
|
||||
|
||||
To create the desktop part of your plugin, initiate a new JavaScript project using `yarn init` and make sure your package name starts with `sonar-plugin-` and a file called `index.js`, which is the entry point to your plugin. A sample `package.json`-file could look like this:
|
||||
|
||||
```
|
||||
{
|
||||
"name": "sonar-plugin-myplugin",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"dependencies": {}
|
||||
}
|
||||
```
|
||||
|
||||
In `index.js` you can now create your plugin. We expect this file to have a default export of type `SonarPlugin`. A hello-world-plugin could look like this:
|
||||
|
||||
```js
|
||||
import {SonarPlugin} from 'sonar';
|
||||
|
||||
export default class extends SonarPlugin {
|
||||
render() {
|
||||
return 'hello world';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Learn more on how to use [Sonar's UI components](ui-components.md).
|
||||
|
||||
### Dynamically loading plugins
|
||||
|
||||
Once a plugin is created, Sonar can load it from it's folder. The path from where the plugins are loaded is specified in `~/.sonar/config.json`. Add the parent folder of your plugin to `pluginPaths` and start Sonar.
|
||||
|
||||
### npm dependencies
|
||||
|
||||
If you need any dependencies in your plugin, you can install them using `yarn add`. The Sonar UI components exported from `sonar`, as well as `react` and `react-dom` don't need to be installed as dependencies. Our plugin-loader makes these dependencies available to your plugin.
|
||||
|
||||
### ES6, babel-transforms and bundling
|
||||
|
||||
Our plugin-loader is capable of all ES6 goodness, flow annotations and JSX and applies the required babel-transforms without you having to care about this. Also you don't need to bundle your plugin, you can simply use ES6 imports and it will work out of the box.
|
||||
|
||||
## Working on the core
|
||||
|
||||
If you only want to work on a plugin, you don't need to run the development build of Sonar, but you can use the production release. However, if you want to contribute to Sonar's core, add additional UI components, or do anything outside the scope of a single plugins this is how you run the development version of Sonar.
|
||||
|
||||
Make sure you have a recent version of node.js and yarn installed on your system (node ≥ 8, yarn ≥ 1.5). Then run the following commands:
|
||||
|
||||
```
|
||||
git clone https://github.com/facebook/Sonar.git
|
||||
cd Sonar
|
||||
yarn
|
||||
yarn start
|
||||
```
|
||||
54
docs/layout-plugin.md
Normal file
54
docs/layout-plugin.md
Normal file
@@ -0,0 +1,54 @@
|
||||
---
|
||||
id: layout-plugin
|
||||
title: Layout Inspector
|
||||
---
|
||||
|
||||
The Layout Inspector in Sonar is useful for a ton of different debugging scenarios. First of all you can inspect what views the hierarchy is made up of as well as what properties each view has. This is incredibly useful when debugging issues with your product.
|
||||
|
||||
The Layout tab supports [Litho](https://fblitho.com) and [ComponentKit](https://componentkit.org) components as well! We integrate with these frameworks to present components in the hierarchy just as if they were native views. We show you all the layout properties, props, and state of the components. The layout inspector is further extensible to support other UI frameworks.
|
||||
|
||||
If you hover over a view or component in Sonar we will highlight the corresponding item in your app! This is perfect for debugging the bounds of your views and making sure you have the correct visual padding.
|
||||
|
||||

|
||||
|
||||
## Setup
|
||||
|
||||
To use the layout inspector plugin, you need to add the plugin to your Sonar client instance.
|
||||
|
||||
### Android
|
||||
|
||||
```java
|
||||
import com.facebook.sonar.plugins.inspector.DescriptorMapping;
|
||||
import com.facebook.sonar.plugins.inspector.InspectorSonarPlugin;
|
||||
|
||||
final DescriptorMapping descriptorMapping = DescriptorMapping.withDefaults();
|
||||
client.addPlugin(new InspectorSonarPlugin(mApplicationContext, descriptorMapping));
|
||||
```
|
||||
|
||||
### iOS
|
||||
|
||||
```objective-c
|
||||
#import <SonarKitLayoutPlugin/SonarKitLayoutPlugin.h>
|
||||
#import <SonarKitLayoutPlugin/SKDescriptorMapper.h>
|
||||
|
||||
SKDescriptorMapper *mapper = [[SKDescriptorMapper alloc] initWithDefaults];
|
||||
[client addPlugin:[[SonarKitLayoutPlugin alloc] initWithRootNode:context.application withDescriptorMapper:mapper]]
|
||||
```
|
||||
|
||||
## Quick edits
|
||||
|
||||
The Layout Inspector not only allows you to view the hierarchy and inspect each item's properties, but it also allows you to edit things such as layout attributes, background colors, props, and state. Most things actually! This allows you to quickly tweak paddings, margins, and colors until you are happy with them, all without re-compiling. This can save you many hours implementing a new design.
|
||||
|
||||
## Target mode
|
||||
|
||||
Enable target mode by clicking on the crosshairs icon. Now, you can touch any view on the device and Layout Inspector will jump to the correct position within your layout hierarchy.
|
||||
|
||||
### Blocking fullscreen views (Android only)
|
||||
|
||||
The issue is that if you have some view that occupies big part of the screen but draws nothing and its Z-position is higher than your main content, then selecting view/component through Layout Inspector doesn't work as you intended, as it will always hit that transparent view and you need to manually navigate to the view you need which is time-consuming and should not be necessary.
|
||||
|
||||
Add the following tag to your view to skip it from Sonar's view picker. The view will still be shown in the layout hierarchy, but it will not be selected while using the view picker.
|
||||
|
||||
```java
|
||||
view.setTag("sonar_skip_view_traversal", true);
|
||||
```
|
||||
18
docs/logs-plugin.md
Normal file
18
docs/logs-plugin.md
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
id: logs-plugin
|
||||
title: Logs
|
||||
---
|
||||
|
||||
The Logs plugin shows device logs, without any additional setup. This is a device plugin, meaning that it is not tied to any specific app and there is no additional setup needed to see the logs.
|
||||
|
||||

|
||||
|
||||
## Filtering
|
||||
|
||||
The search bar can be used to search for logs and filter for certain types. From the context menu on the table headers you can show more infos like timestamp, PID or TID. Click on a tag, PID or TID in the table to filter only for logs with the same value.
|
||||
|
||||
## Expression watcher
|
||||
|
||||
The expression watcher in the sidebar can be used to watch for certain logs to happen and count how often the occur. An expression can be a simple string, or a regular expression which is matched against the logs.
|
||||
|
||||
When the notify checkbox is enabled, Sonar will send notifications once the log is happening. This let's you know when the watcher triggered, even if Sonar is in the background.
|
||||
32
docs/network-plugin.md
Normal file
32
docs/network-plugin.md
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
id: network-plugin
|
||||
title: Network
|
||||
---
|
||||
|
||||
Use the Network inspector to inspect outgoing network traffic our apps. You can easily browse all requests being made and their responses. The plugin also supports gzipped responses.
|
||||
|
||||

|
||||
|
||||
## Setup
|
||||
|
||||
To use the network plugin, you need to add the plugin to your Sonar client instance.
|
||||
|
||||
### Android
|
||||
|
||||
```java
|
||||
import com.facebook.sonar.plugins.network.NetworkSonarPlugin;
|
||||
|
||||
client.addPlugin(new NetworkSonarPlugin());
|
||||
```
|
||||
|
||||
### iOS
|
||||
|
||||
```objective-c
|
||||
#import <SonarKitNetworkPlugin/SonarKitNetworkPlugin.h>
|
||||
|
||||
[client addPlugin: [SonarKitNetworkPlugin new]]
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
All request sent from the device will be listed in the plugin. Click on a request to see details like headers and body. You can filter the table for domain, method or status by clicking on the corresponding value in the table.
|
||||
50
docs/send-data.md
Normal file
50
docs/send-data.md
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
id: send-data
|
||||
title: Sending Data to Plugins
|
||||
sidebar_label: Send Data
|
||||
---
|
||||
|
||||
It is often useful to get an instance of a sonar plugin to send data to it. Sonar makes this simple with built in support.
|
||||
|
||||
Plugins should be treated as singleton instances as there can only be one `SonarClient` and each `SonarClient` can only have one instance of a certain plugin. The Sonar API makes this simple by offering a way to get the current client and query it for plugins.
|
||||
|
||||
Plugins are identified by the string that their identifier method returns, in this example, "MySonarPlugin":
|
||||
|
||||
### Android
|
||||
|
||||
```java
|
||||
final SonarClient client = AndroidSonarClient.getInstance(context);
|
||||
// Client may be null if AndroidSonarClient.createInstance() was never called
|
||||
// which is the case in production builds.
|
||||
if (client != null) {
|
||||
final MySonarPlugin plugin = client.getPlugin("MySonarPlugin");
|
||||
plugin.sendData(myData);
|
||||
}
|
||||
```
|
||||
|
||||
### iOS
|
||||
|
||||
```objective-c
|
||||
SonarClient *client = [SonarClient sharedClient];
|
||||
MySonarPlugin *myPlugin = [client pluginWithIdentifier:@"MySonarPlugin"];
|
||||
[myPlugin sendData:myData];
|
||||
```
|
||||
|
||||
### C++
|
||||
|
||||
```c++
|
||||
auto &client = SonarClient::instance();
|
||||
|
||||
// "MySonarPlugin is the return value of MySonarPlugin::identifier()
|
||||
auto aPlugin = client.getPlugin("MySonarPlugin");
|
||||
|
||||
// aPlugin is a std::shared_ptr<SonarPlugin>. Downcast to expected type.
|
||||
auto myPlugin = std::static_pointer_cast<MySonarPlugin>(aPlugin);
|
||||
|
||||
// Alternatively, use the templated version
|
||||
myPlugin = client.getPlugin<MySonarPlugin>("MySonarPlugin");
|
||||
|
||||
myPlugin->sendData(myData);
|
||||
```
|
||||
|
||||
Here, `sendData` is an example of a method that might be implemented by the sonar plugin.
|
||||
11
docs/stetho.md
Normal file
11
docs/stetho.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
id: stetho
|
||||
title: Stetho Guidance
|
||||
sidebar_label: Stetho Guidance
|
||||
---
|
||||
|
||||
In 2015, we introduced [Stetho](http://facebook.github.io/stetho/), an Android debugging bridge built on top of Chrome dev tools. While it was a valuable tool to us and many members of the community, we felt that it limited us in what we could do with it. Stetho is Android-only and while Chrome dev tools gave us a nice foundation to build upon, they also limited us in what we could build. Stetho is an Android tool and Chrome dev tools is built for web developers. This means we can only provide a good experience for the intersection of those two development environments, which was very limiting. With Sonar being built as a standalone app, we can do more things, like handling adb connections and supporting iOS, which wouldn't easily achievable with stetho.
|
||||
|
||||
This is why we built Sonar. We wanted to create a platform that gives us all the flexibility we need to build more advanced features and support for iOS. One of Sonar's core concept is it's extensibility using [plugins](create-plugin.md). Plugins are written in react and we provide a set of ready-to-use UI components that allows developers to build great plugin UIs with a few lines of code.
|
||||
|
||||
While offering many new features, Sonar also already covers most of the features provided by Stetho. This includes network and layout inspection, and an advanced log viewer. We are committed to continuously improving Sonar with new features and plugins, however we are aware that not all stetho features are currently supported by Sonar. If you are using a particular feature of Stetho which isn't supported by Sonar, we are more than happy to hear about your use-case. Stetho will continue to work and we are not abandoning it so you can choose the tool that fits your needs best. We are confident that Sonar will work well for most use-cases and are more than happy to accept contributions from the open-source community adding new features.
|
||||
75
docs/styling-components.md
Normal file
75
docs/styling-components.md
Normal file
@@ -0,0 +1,75 @@
|
||||
---
|
||||
id: styling-components
|
||||
title: Styling Components
|
||||
sidebar_label: Styling Components
|
||||
---
|
||||
|
||||
We use a styled-component based approach to styling our views. This means styles are defined in JavaScript and are written as CSS-stylesheets to the DOM. A component and it's styles are coupled. Styled components can extend another to inherit their styles.
|
||||
|
||||
## Basic tags
|
||||
|
||||
For basic building blocks (views, texts, ...) you can use the styled object.
|
||||
|
||||
```javascript
|
||||
import {styled} from 'sonar';
|
||||
|
||||
const MyView = styled.view({
|
||||
fontSize: 10,
|
||||
color: colors.red
|
||||
});
|
||||
const MyText = styled.text({ ... });
|
||||
const MyImage = styled.image({ ... });
|
||||
const MyInput = styled.input({ ... });
|
||||
```
|
||||
|
||||
But you can use any other HTML-tags like this:
|
||||
|
||||
```javascript
|
||||
styled.customHTMLTag('canvas', { ... });
|
||||
```
|
||||
|
||||
## Extending Sonar Components
|
||||
|
||||
It's very common for components to require customizing Sonar's components in some way. For example changing colors, alignment, or wrapping behavior. There is a `extends` method on all styled components which allows adding or overwriting existing style rules.
|
||||
|
||||
For these use cases when a styled component is only used within the context of a single component we encourage declaring it as a inner static instance. This makes it clear where the component is used and avoids polluting the global namespace.
|
||||
|
||||
```javascript
|
||||
class MyComponent extends Component {
|
||||
static Container = FlexRow.extends({
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
render() {
|
||||
return <MyComponent.Container>...</MyComponent.Container>;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## CSS
|
||||
|
||||
The CSS-in-JS object passed to the styled components takes just any CSS rule, with the difference that it uses camel cased keys for the properties. Pixel-values can be numbers, any other values need to be strings.
|
||||
|
||||
Dynamic values also can be functions, receiving the react props as argument. (Make sure to add properties passed to a component to the `ignoredAttributes` array to not be written to the DOM as an attribute.)
|
||||
|
||||
```javascript
|
||||
const MyView = styled.view(
|
||||
{
|
||||
fontSize: 10,
|
||||
color: props => (props.disabled ? colors.red : colors.black),
|
||||
},
|
||||
{
|
||||
ignoredAttributes: ['disabled'],
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Pseudo-classes can be used like this:
|
||||
|
||||
```javascript
|
||||
'&:hover': {color: colors.red}`
|
||||
```
|
||||
|
||||
## Colors
|
||||
|
||||
The colors module contains all standard colors used by Sonar. All the available colors are defined in `src/ui/components/colors.js` with comments about suggested usage of them. And we strongly encourage to use them.
|
||||
96
docs/testing.md
Normal file
96
docs/testing.md
Normal file
@@ -0,0 +1,96 @@
|
||||
---
|
||||
id: testing
|
||||
title: Testing
|
||||
sidebar_label: Testing
|
||||
---
|
||||
|
||||
Developer tools are only used if they work. We have built APIs to test plugins.
|
||||
|
||||
## Android
|
||||
|
||||
Start by creating your first test file in this directory `MySonarPluginTest.java`. In the test method body we create our plugin which we want to test as well as a `SonarConnectionMock`. In this contrived example we simply assert that our plugin's connected status is what we expect.
|
||||
|
||||
```java
|
||||
@RunWith(WithTestDefaultsRunner.class)
|
||||
public class MySonarPluginTest {
|
||||
|
||||
@Test
|
||||
public void myTest() {
|
||||
final MySonarPlugin plugin = new MySonarPlugin();
|
||||
final SonarConnectionMock connection = new SonarConnectionMock();
|
||||
|
||||
plugin.onConnect(connection);
|
||||
assertThat(plugin.connected(), equalTo(true));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There are two mock classes that are used to construct tests `SonarConnectionMock` and `SonarResponderMock`. Together these can be used to write very powerful tests to verify the end to end behavior of your plugin. For example we can test if for a given incoming message our plugin responds as we expect.
|
||||
|
||||
```java
|
||||
@Test
|
||||
public void myTest() {
|
||||
final MySonarPlugin plugin = new MySonarPlugin();
|
||||
final SonarConnectionMock connection = new SonarConnectionMock();
|
||||
final SonarResponderMock responder = new SonarResponderMock();
|
||||
|
||||
plugin.onConnect(connection);
|
||||
|
||||
final SonarObject params = new SonarObject.Builder()
|
||||
.put("phrase", "sonar")
|
||||
.build();
|
||||
connection.receivers.get("myMethod").onReceive(params, responder);
|
||||
|
||||
assertThat(responder.successes, hasItem(
|
||||
new SonarObject.Builder()
|
||||
.put("phrase", "ranos")
|
||||
.build()));
|
||||
}
|
||||
```
|
||||
|
||||
## C++
|
||||
|
||||
Start by creating your first test file in this directory `MySonarPluginTests.cpp` and import the testing utilities from `//xplat/sonar-client:SonarTestLib`. These utilities mock out core pieces of the communication channel so that you can test your plugin in isolation.
|
||||
|
||||
```
|
||||
#include <MySonarPlugin/MySonarPlugin.h>
|
||||
#include <SonarTestLib/SonarConnectionMock.h>
|
||||
#include <SonarTestLib/SonarResponderMock.h>
|
||||
|
||||
#include <folly/json.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace sonar {
|
||||
namespace test {
|
||||
|
||||
TEST(MySonarPluginTests, testDummy) {
|
||||
EXPECT_EQ(1 + 1, 2);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace sonar
|
||||
} // namespace facebook
|
||||
```
|
||||
|
||||
Here is a simple test using these mock utilities to create a plugin, send some data, and assert that the result is as expected.
|
||||
|
||||
```
|
||||
TEST(MySonarPluginTests, testDummy) {
|
||||
std::vector<folly::dynamic> successfulResponses;
|
||||
auto responder = std::make_unique<SonarResponderMock>(&successfulResponses);
|
||||
auto conn = std::make_shared<SonarConnectionMock>();
|
||||
|
||||
MySonarPlugin plugin;
|
||||
plugin.didConnect(conn);
|
||||
|
||||
folly::dynamic message = folly::dynamic::object("param1", "hello");
|
||||
folly::dynamic expectedResponse = folly::dynamic::object("response", "Hi there");
|
||||
|
||||
auto receiver = conn->receivers_["someMethod"];
|
||||
receiver(message, std::move(responder));
|
||||
|
||||
EXPECT_EQ(successfulResponses.size(), 1);
|
||||
EXPECT_EQ(successfulResponses.back(), expectedResponse);
|
||||
}
|
||||
```
|
||||
254
docs/ui-components.md
Normal file
254
docs/ui-components.md
Normal file
@@ -0,0 +1,254 @@
|
||||
---
|
||||
id: ui-components
|
||||
title: UI Components
|
||||
sidebar_label: UI Components
|
||||
---
|
||||
|
||||
Sonar has a lot of built in React components to build UIs. You can find all of these in [`src/ui/components`](https://github.com/facebook/Sonar/tree/master/src/ui/components) and can import them directly using `import {Buttom} from 'sonar'`.
|
||||
|
||||
## FlexBox
|
||||
|
||||
In Sonar we make heavy use of flexbox for layout. FlexBox is layout system on the web which has been specifically design for building application like UIs. Sonar provides two flexbox components `FlexRow` and `FlexColumn`. These are flexbox components with some sane defaults such as automatically scrolling content that overflows.
|
||||
|
||||
```javascript
|
||||
import { FlexRow, FlexColumn } from 'sonar';
|
||||
|
||||
// Align children horizontally
|
||||
<FlexRow>
|
||||
{children}
|
||||
</FlexRow>
|
||||
|
||||
// Align children vertically
|
||||
<FlexColumn>
|
||||
{children}
|
||||
</FlexColumn>
|
||||
```
|
||||
|
||||
To control other flexbox properties than the direction you can extend existing components, detailed in [Styling Components](styling-components.md).
|
||||
|
||||
```javascript
|
||||
import {FlexRow, styled} from 'sonar';
|
||||
|
||||
const CenterFlexRow = FlexRow.extends({
|
||||
justifyContent: 'center',
|
||||
});
|
||||
|
||||
// Align children horizontally in the center
|
||||
<CenterFlexRow>{children}</CenterFlexRow>;
|
||||
```
|
||||
|
||||
## Text
|
||||
|
||||
The `Text` component is available to render any text in your plugin. To render headers and subtitle differently for example, we used the styled module. With this we can also change the color, text alignment, and any other properties typically found on a `span`.
|
||||
|
||||
```javascript
|
||||
import {Text, styled, colors} from 'sonar';
|
||||
|
||||
const Title = Text.extends({
|
||||
color: colors.red,
|
||||
});
|
||||
|
||||
<Title code={true}>Sonar Subtitle</Title>;
|
||||
```
|
||||
|
||||
## Buttons
|
||||
|
||||
Sonar comes with a couple of button styles built in! As always you can style then further using the styled module but we expect the pre-defined buttons to fit most UIs.
|
||||
|
||||
```javascript
|
||||
import {Button} from 'sonar';
|
||||
|
||||
<Button onClick={this.onClick} icon="airport" compact={true}>
|
||||
Click Me!
|
||||
</Button>;
|
||||
```
|
||||
|
||||
You can create a group of buttons by surrounding it with `<ButtonGroup>`.
|
||||
|
||||
## Sidebar
|
||||
|
||||
The `Sidebar` component provides a nice abstraction around some common but complex behavior. The `Sidebar` is used by all major Sonar plugins and using it in your plugin will ensure your plugin behaves similarly, such as allowing for resizing.
|
||||
|
||||
```javascript
|
||||
import {FlexRow, Sidebar, colors, styled} from 'infinity-ui';
|
||||
import {SonarPlugin} from 'sonar';
|
||||
|
||||
type State = {};
|
||||
|
||||
export default class MySonarPlugin extends SonarPlugin<State> {
|
||||
static title = 'My Plugin';
|
||||
static id = 'my-plugin';
|
||||
|
||||
static Red = styled.view({
|
||||
backgroundColor: colors.red,
|
||||
});
|
||||
|
||||
static Blue = styled.view({
|
||||
backgroundColor: colors.blue,
|
||||
});
|
||||
|
||||
render() {
|
||||
return (
|
||||
<FlexRow fill={true}>
|
||||
<MySonarPlugin.Red fill={true} />
|
||||
|
||||
<Sidebar position="right" width={400} minWidth={300}>
|
||||
<MySonarPlugin.Blue fill={true} />
|
||||
</Sidebar>
|
||||
</FlexRow>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Panel
|
||||
|
||||
Panels are a way to section data, and make it collapsible. They are often used in sidebars. Just give the Panel a heading and some content and it makes sure that it displays in the same style as the rest of Sonar.
|
||||
|
||||
```javascript
|
||||
import {
|
||||
FlexColumn,
|
||||
FlexRow,
|
||||
Sidebar,
|
||||
Panel,
|
||||
colors,
|
||||
styled,
|
||||
SonarPlugin,
|
||||
} from 'sonar';
|
||||
|
||||
type State = {};
|
||||
|
||||
export default class MySonarPlugin extends SonarPlugin<State> {
|
||||
static title = 'My Plugin';
|
||||
static id = 'my-plugin';
|
||||
|
||||
static Red = styled.view({
|
||||
backgroundColor: colors.red,
|
||||
});
|
||||
|
||||
static Blue = styled.view({
|
||||
backgroundColor: colors.blue,
|
||||
height: 200,
|
||||
});
|
||||
|
||||
static Green = styled.view({
|
||||
backgroundColor: colors.green,
|
||||
height: 200,
|
||||
});
|
||||
|
||||
render() {
|
||||
return (
|
||||
<FlexRow fill={true}>
|
||||
<MySonarPlugin.Red fill={true} />
|
||||
|
||||
<Sidebar position="right" width={400} minWidth={300}>
|
||||
<FlexColumn>
|
||||
<Panel heading={'Blue'} floating={false}>
|
||||
<MySonarPlugin.Blue />
|
||||
</Panel>
|
||||
|
||||
<Panel heading={'Green'} floating={false}>
|
||||
<MySonarPlugin.Green />
|
||||
</Panel>
|
||||
</FlexColumn>
|
||||
</Sidebar>
|
||||
</FlexRow>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## DataInspector
|
||||
|
||||
The `DataInspector` component is used to unpack and display a javascript object. It is used to show View properties in the layout inspector, and to show event data in the analytics plugins.
|
||||
|
||||
```javascript
|
||||
import {FlexColumn, DataInspector, SonarPlugin} from 'sonar';
|
||||
|
||||
type State = {};
|
||||
|
||||
export default class MySonarPlugin extends SonarPlugin<State> {
|
||||
static title = 'My Plugin';
|
||||
static id = 'my-plugin';
|
||||
|
||||
static data = {
|
||||
one: 1,
|
||||
two: '2',
|
||||
three: [1, 2, 3],
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<FlexColumn fill={true}>
|
||||
<DataInspector data={MySonarPlugin.data} />
|
||||
</FlexColumn>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Toolbar
|
||||
|
||||
The `Toolbar` component can display a toolbar with buttons, inputs, etc. A `<Spacer />` can be used to fill the space between items.
|
||||
|
||||
```javascript
|
||||
import {Toolbar, Spacer, Button, SonarPlugin} from 'sonar';
|
||||
|
||||
export default class MySonarPlugin extends SonarPlugin<State> {
|
||||
render() {
|
||||
return (
|
||||
<Toolbar fill={true}>
|
||||
<Button>Button A</Button>
|
||||
<Spacer />
|
||||
<Button>Button B</Button>
|
||||
</Toolbar>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Popover
|
||||
|
||||
Used to display content in an overlay.
|
||||
|
||||
```javascript
|
||||
import {Popover, SonarPlugin} from 'sonar';
|
||||
|
||||
export default class MySonarPlugin extends SonarPlugin<State> {
|
||||
render() {
|
||||
return (
|
||||
{this.state.popoverVisible && <Popover onDismiss={() => this.setState({popoverVisible: false})}>
|
||||
...
|
||||
</Popover >}
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## ContextMenu
|
||||
|
||||
Add a native context menu to a component by wrapping it with the ContextMenu component.
|
||||
|
||||
```javascript
|
||||
import {ContextMenu, SonarPlugin} from 'sonar';
|
||||
|
||||
export default class MySonarPlugin extends SonarPlugin<State> {
|
||||
contextMenuItems = [
|
||||
{
|
||||
label: 'Copy',
|
||||
click: this.copy,
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
{
|
||||
label: 'Clear All',
|
||||
click: this.clear,
|
||||
},
|
||||
];
|
||||
|
||||
render() {
|
||||
return <ContextMenu items={this.contextMenuItems}>...</ContextMenu>;
|
||||
}
|
||||
}
|
||||
```
|
||||
13
docs/unterstanding-sonar.md
Normal file
13
docs/unterstanding-sonar.md
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
id: understand
|
||||
title: Understanding Sonar
|
||||
sidebar_label: Understanding Sonar
|
||||
---
|
||||
|
||||
The Sonar desktop app and the mobile native SDK establish an [rsocket](http://rsocket.io) connection which is used to send data to and from the device. Sonar does not make any restrictions on what kind of data is being sent. This enables a lot of different use-cases where you want to better understand what is going inside your app. For example you can visualize the state of local caches, events happening or trigger actions on your app from the desktop.
|
||||
|
||||
## Plugins
|
||||
|
||||
Sonar itself only provides the architectural platform. What makes it useful are the plugins built on top of it: [Logs](logs-plugin.md), [Layout Inspector](layout-plugin.md) and [Network Inspector](network-plugin.md) are all plugins. Plugins can be built very specific to your business logic and the use-cases you have in your app. We are shipping Sonar with a couple of built-in all-purpose plugins, but we encourage you to build your own.
|
||||
|
||||
A plugin always consists of the native implementation sending and receiving data and the desktop plugin visualizing data. Learn more on how to [create a plugin](create-plugin.md). The native implementations are written in Java, Objective-C, or C++, the desktop UI is written in React.
|
||||
Reference in New Issue
Block a user