b1serviceflayer 1.0.3

  • Readme
  • Changelog
  • Example
  • Installing
  • 48

A library for Dart developers that makes SAP BusinessOne(B1) Service Layer (SL) programming a lot simpler than using plain http package. B1 is one of the most popular information systems for small and mid sized companies. It runs on Microsoft SQL Server as well as on SAP HANA. SL is available only for the HANA version. B1 is a commercial product, and if you want to be a developer, you should contact a SAP Partner or SAP to become a partner. This Dart package is only for developers who are part of this community. If you work for a company that already has B1, this package could be very helpful, too, for building custom applications, since then you are entitled to use SL. Official Service Layer documentation is available only if you have an S user ID. On the other hand, a six part Service Layer API video is publicly available.

The f in the name was inspired by the name sqflite.

Created from templates made available by Stagehand under a BSD-style license.

SAP, SAP Business One are registered trademarks of SAP

Usage #

For a full working example review the example folder as well as the nice collection of automated Dart test scripts in the test folder. Here is a typical skeleton of an application using the functionality of the package:

import 'dart:convert';
import 'package:b1serviceflayer/b1serviceflayer.dart';

main() async {
  ...
  final b1s = B1ServiceLayer(B1Connection(serverUrl: url, 
    companyDB: companyDB, userName: user, password: pwd));
  try {    
    await b1s.loginAsync().timeout(const Duration(seconds: 10));
    ...
    String activitiesJson = await b1s.queryAsync("Activities");
    Map<String, dynamic> activitiesMap = json.decode(activitiesJson);
    ...
    await b1s.updateAsync(entityName: "Activities($activityCodeToComplete)",
      entityJSON: json.encode(activityList[0]));
    ...
    String activityJson = await b1s.queryAsync("Activities($activityCodeToComplete)");
    ...
    activityJson =   return await b1s.createAsync(entityName: "Activities",
      entityJSON: json.encode(activityData));
    ...
    await b1s.deleteAsync(entityName: "Activities($activityCodeToComplete)",
      errorWhenDoesntExist: true);
    ...
    await b1s.logoutAsync();
  } on B1Error catch(exception) {
      print("Exception is B1Error (${exception.statusCode}) ${exception.error.message.value} (${exception.error.code}) for Query ${exception.queryUrl}");
      print("Payload ${exception.postBody}");
  } catch(exception,stackTrace) {
    print(exception); print(stackTrace);
  }
}

All SL invocation is performed via the regular, and brilliant, Dart http package. All functions return a Future, which can be used with the regular await keywords in an async body; this is the reason all function names is suffixed with Async as reminder, that these should be invoket with await, or the classic Future handling ways.

The main class is B1ServiceLayer, which requires a B1Connection object. The B1Connection is initialiyed with the four mandatory SL login parameters. Here is the list of the functions:

  • loginAsync the library automatically calls login when the session is expired after 30 minutes. So, all subsequent calls of the same service layer object use the same connection, but after 30 minutes, a relogin is automatically performed.
  • queryAsync returns a list of objects or a single one, depending on the query string.
  • createAsync creates an B1 entity object in B1, and returns the full details of the created object.
  • updateAsync updates a B1 entity object. Make sure to pass only the values that you really want to update, otherwise you may overwrite some valuable data by mistake.
  • deleteAsync deletes a B1 entity object. Very few objects are allowed to be deleted in SAP. Activities are very good for tests, since they are deletable.
  • logoutAsync Sessions are automatically timeouted in B1 SL, so calling logout is required only when the user is changed.

Only queryAsync and createAsync have return value, a JSON string, which can be parsed with the dart json.decode function into a Map<String,dynamic>. The functions createAsync and updateAsync are expecting JSON strings as input parameters. The json.encode function can be used to concvert Map<String,dynamic> objects into JSON strings. These functions are meant for rudimentary JSON communication, they don't parse the JSON string, except the error responses. This layer gives the ultimate performance, as much as possible with the SL and Dart infrastructure. If you want a higher level of convenience you can use JSON Dart conversion generators. For example, the B1Error class was generated with quicktype, but other tools can be used either. Pay attention, however that the B1 JSON structures are big, and a performance may be a problem with these code generators.

In future versions additional functions are added for batch execution, image uploading.

You can use the Future's timeout to control the tolerable duration in your application. I was experimenting with using the connectionTimeout of the HttpClient of dart:io, but the Future timeout is a lot more convenient. When SL returns an error response, B1ServiceLayer functions throw B1Error. Upon timeout or network errors the corresponding exceptions are thrown by the underlying Dart machinery. B1Error has a nice bunch of properties for error analysis, which makes programming clean.

Performance #

Here is a log of the sample application from the example folder:

Logging in ...
Querying activities ...
9 activities returned in 5274 ms
Activity 8 Completed -3 Notes Completed on 2019-08-05 21:17:13.951161 in 53 ms
Activity created with code 15 Notes A new job to do in 147 ms
Querying activities with newly added activity...
Number of activities 9 returned in 102 ms

The first operation was slow, even after an explicit login, but then the performance was quite acceptable.

License #

The license is an open source BSD license as suggested by the Dart package publishing documentation, since Dart itself has BSD license.

See the LICENSE.md file.

Features and bugs #

Please file feature requests and bugs at the issue tracker.

1.0.0 #

  • The project was initated with Stagehand, and then a nicely working version is available with this version. Tests have been added as well as an example. Breaking changes are not expected.

1.0.1 #

  • Documentation has been extended significantly.
  • Since logins are so important to monitor, a printLogins optional bool parameter added to the B1ServiceLayer constructor, which enables printing a line in the console for debugging purposes.
  • executionMilliseconds property added to B1ServiceLayer to help monitoring performance, which is so crytical in mobile applications.
  • The example and some of the test scripts have been adjusted accordingly.

1.0.2 #

  • tYES, tNO relocated into a new BoYesNoEnum class.
  • BoEnums abstract class was added as a base class for defining typesafe string enumeration types.

1.0.3 #

  • Unfortunately, flutter_test depends on meta 1.1.6 today 2019.08.09 and I decremented the meta library version to 1.1.6.

example/b1serviceflayer_example.dart

import 'dart:convert';
import 'package:b1serviceflayer/b1serviceflayer.dart';

main() async {
  const url = "http://hana93srv:50001/b1s/v1/";
  const user = "manager";
  const pwd ="123qwe";
  const companyDB ="SBODEMOUS";
  final b1s = B1ServiceLayer(B1Connection(serverUrl: url, 
    companyDB: companyDB, userName: user, password: pwd),printLogins: true);
  try {
    print("Logging in ...");
    await b1s.loginAsync().timeout(const Duration(seconds: 10));

    print("Querying activities ...");
    String activitiesJson = await b1s.queryAsync("Activities");
    Map<String, dynamic> activitiesMap = json.decode(activitiesJson);
    List<dynamic> activityList = activitiesMap["value"];
    print('${activityList.length} activities returned in ${b1s.exetutionMilliseconds} ms');
    activityList.forEach((activity){
      print('Activity ${activity["ActivityCode"]} Completed ${activity["Status"]} Notes ${activity["Notes"]} ');
    });

    print("Updating activity ...");
    int activityCodeToComplete = activityList[0]["ActivityCode"];
    activityList[0]["Status"] = -3;
    activityList[0]["Notes"] = "Completed on ${DateTime.now()}";
    await b1s.updateAsync(entityName: "Activities($activityCodeToComplete)",
      entityJSON: json.encode(activityList[0]));

    print("Fetching updated activity ...");
    String activityJson = await b1s.queryAsync("Activities($activityCodeToComplete)");
    Map<String, dynamic> activityMap = json.decode(activityJson);
    print('Activity ${activityMap["ActivityCode"]} Completed ${activityMap["Status"]} Notes ${activityMap["Notes"]} in ${b1s.exetutionMilliseconds} ms');

    print("Adding a new activity ...");
    activityJson = await addActivity(b1s, 12);
    activityMap = json.decode(activityJson);
    print('Activity created with code ${activityMap["ActivityCode"]} Notes ${activityMap["Notes"]} in ${b1s.exetutionMilliseconds} ms');

    print("Deleting the new activity ...");
    activityCodeToComplete = activityMap["ActivityCode"];
    await b1s.deleteAsync(entityName: "Activities($activityCodeToComplete)", errorWhenDoesntExist: true);

    print("Querying activities with newly added activity...");
    activitiesJson = await b1s.queryAsync("Activities");
    activitiesMap = json.decode(activitiesJson);
    activityList = activitiesMap["value"];
    print('Number of activities ${activityList.length} returned in ${b1s.exetutionMilliseconds} ms');
    activityList.forEach((activity){
      print('Activity ${activity["ActivityCode"]} Completed ${activity["Status"]} Notes ${activity["Notes"]} ');
    });

    print("Logging out ...");
    await b1s.logoutAsync();
  } on B1Error catch(exception) {
      print("Exception is B1Error (${exception.statusCode}) ${exception.error.message.value} (${exception.error.code}) for Query ${exception.queryUrl}");
      print("Payload ${exception.postBody}");
  } catch(exception,stackTrace) {
    print(exception); print(stackTrace);
  }
}
Future<String> addActivity(B1ServiceLayer b1s, int userId) async {
  final Map<String,dynamic> activityData = {};
  //Subject:-1, ActivityType: -1 /*General*/, Status: -2 /*NotStarted, -3=Completed*/,
  activityData["Notes"] = "A new job to do";
  activityData["ActivityDate"] = "${DateTime.now()}";
  activityData["ActivityTime"] = "08:08:08"; //Activity creation date/time
  activityData["StartDate"] =  "${DateTime(2019,9,7)}"; //Automatically convert Dates and Times to strings in POST/PUT/PATCH
  activityData["StartTime"] = "10:10"; //UTC 0 offset, not the local time :(
  activityData["Details"] = "${DateTime.now()}: more explanation";
  activityData["ActivityType"] = -1;
  activityData["Activity"] = "cn_Task";
  activityData["Priority"] = "pr_High";
  activityData["PersonalFlag"] = BoYesNoEnum.tNO.value;
  activityData["DurationType"] = "du_Hours"; 
  activityData["Duration"] = 48.0;
  activityData["HandledBy"] =  userId; //A user number
  return await b1s.createAsync(entityName: "Activities",
    entityJSON: json.encode(activityData));
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  b1serviceflayer: ^1.0.3

2. Install it

You can install packages from the command line:

with pub:


$ pub get

with Flutter:


$ flutter pub get

Alternatively, your editor might support pub get or flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:b1serviceflayer/b1serviceflayer.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
0
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
90
Overall:
Weighted score of the above. [more]
48
Learn more about scoring.

We analyzed this package on Oct 16, 2019, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.5.1
  • pana: 0.12.21

Platforms

Detected platforms: Flutter, other

Primary library: package:b1serviceflayer/b1serviceflayer.dart with components: io.

Health issues and suggestions

Document public APIs. (-0.14 points)

66 out of 68 API elements have no dartdoc comment.Providing good documentation for libraries, classes, functions, and other API elements improves code readability and helps developers find and use your API.

Format lib/src/b1enumtypes.dart.

Run dartfmt to format lib/src/b1enumtypes.dart.

Format lib/src/b1serviceflayer_base.dart.

Run dartfmt to format lib/src/b1serviceflayer_base.dart.

Format lib/src/quicktype_error.dart.

Run dartfmt to format lib/src/quicktype_error.dart.

Format lib/src/quicktype_session.dart.

Run dartfmt to format lib/src/quicktype_session.dart.

Maintenance issues and suggestions

Support latest dependencies. (-10 points)

The version constraint in pubspec.yaml does not support the latest published versions for 1 dependency (intl).

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.4.0 <3.0.0
http ^0.12.0+2 0.12.0+2
intl ^0.15.8 0.15.8 0.16.0
meta ^1.1.6 1.1.7
Transitive dependencies
async 2.4.0
charcode 1.1.2
collection 1.14.12
http_parser 3.1.3
path 1.6.4
source_span 1.5.5
string_scanner 1.0.5
term_glyph 1.1.0
typed_data 1.1.6
Dev dependencies
pedantic ^1.7.0 1.8.0+1
test ^1.6.0