This is the first article on learning from Scratch to develop a Flutter App.
This article introduces the basic features of Dart. The purpose of this article is to give you a general understanding of the Dart language and a preliminary grasp of the Dart syntax.
We assume that you already have some programming background, and if you know an object-oriented language like JavaScript or Java, Dart should be very hands-on to learn.
Dart is a programming language that takes a broad approach. Although Dart has a lot of syntax similar to JavaScript, it is also a strongly typed language that combines features of strongly typed object-oriented languages like Java to make it suitable for large-scale application development without the bloat of Java. The Dart language is simple, flexible, and efficient in design.
From simple browser script to nodeJS, JavaScript gradually extends to PC client (electron), App (React Native) and even small program development. It has become a full-stack development language in a real sense.
If JavaScript has grown barbarously over time, Dart was carefully designed from the start. If there’s one language to take JavaScript’s place, it’s probably Dart.
Talk is Cheep, let’s take a look at the language for ourselves.
variable
You can declare a variable like JavaScript:
var name = 'Bob';
Copy the code
The compiler deduces that name is of type String, equivalent to:
String name = 'Bob';
Copy the code
Dart is a strongly typed language, as shown in the following code:
var name = 'Bob';
// Call the String method
print(name.toLowerCase());
// Error compiling
// name = 1;
Copy the code
As we mentioned earlier, Dart can also be very flexible. If you want to change the type of a variable, you can also use dynamic to declare the variable, just like JavaScript:
dynamic name = 'Bob'; / / type String
name = 1;/ / int type
print(name);
Copy the code
The code above compiles and runs fine, but don’t use it unless you have a good reason to.
Final has the same semantics as Java, meaning that the variable is immutable:
// String can be omitted
final String name = 'Bob';
// Error compiling
// name = 'Mary';
Copy the code
Where String can be omitted, the Dart compiler is smart enough to know the type of the variable name.
If you want to declare constants, you can use the const keyword:
const PI = '3.14';
class Person{
static const name = 'KK';
}
Copy the code
If it is a class variable, it needs to be declared static const.
Built-in types
Unlike Java, the type is particularly detailed, such as the integer type, byte, short, int, long. One of the things that makes Dart easy to use is its simplicity of type design, which can be interpreted as sacrificing space for efficiency.
Numeric types
Dart has built-in support for two numeric types, int and double, both 64-bit in size.
var x = 1;
// 0x starts with a hexadecimal integer
var hex = 0xDEADBEEF;
var y = 1.1;
// in exponential form
var exponents = 1.42 e5;
Copy the code
Note that in Dart, all variable values are an object, including int and double, which are subclasses of num, unlike Java and JavaScript:
// String -> int
var one = int.parse('1');
assert(one == 1);
// String -> double
var onePointOne = double.parse('1.1');
assert(onePointOne == 1.1);
// int -> String
String oneAsString = 1.toString();
assert(oneAsString == '1');
// double -> String
String piAsString = 3.14159.toStringAsFixed(2);
assert(piAsString == '3.14');
Copy the code
string
The Dart string uses UTF-16 encoding.
var s = 'in';
s.codeUnits.forEach((ch) => print(ch));
// Outputs UNICODE values
20013
Copy the code
Dart uses JavaScript’s template-like string concept to insert variables in strings using the ${expression} syntax:
var s = "hello";
print('${s}, world! ');
// Can be simplified to:
print('$s, world! ');
// Call the method
print('${s.toUpperCase()}, world! ');
Copy the code
Dart can compare strings directly with == :
var s1 = "hello";
var s2 = "HELLO";
assert(s1.toUpperCase() == s2);
Copy the code
Boolean type
The Dart Boolean type corresponds to the bool keyword, which has true and false values, similar to those of other languages. It’s worth noting that Dart conditional statements like if and assert must have bool values, unlike JavaScript.
var s = ' ';
assert(s.isEmpty);
if(s.isNotEmpty){
// do something
}
// compiler error, used in JavaScript to identify undefined
if(s){
}
Copy the code
Lists
You can map a List in Dart to a JavaScript array or an ArrayList in Java, but Dart is more elaborate.
You can declare an array object like JavaScript:
var list = [];
list.add('Hello');
list.add(1);
Copy the code
Here the List accepts type Dynamic, and you can add any type of object to it, but declare it like this:
var iList = [1.2.3];
iList.add(4);
The argument type 'String' can't be assigned to The parameter type 'int'
//iList.add('Hello');
Copy the code
Dart then deduces that the List is a List
, from which the List can only accept ints. You can also explicitly declare the type of the List:
var sList = List<String> ();// In the Flutter library, there are many variable declarations like this:
List<Widget> children = const <Widget>[];
Copy the code
Const const = ‘children’; const const = ‘children’; const = ‘children’; const = ‘children’;
var constList = const <int> [1.2];
constList[0] = 2; // Compile passed, run error
constList.add(3); // Compile passed, run error
Copy the code
Dart2.3 added the spread operator… And… ? You can easily see how they are used in this example:
var list = [1.2.3];
var list2 = [0. list];assert(list2.length == 4);
Copy the code
If the extension object may be NULL, you can use… ? :
var list;
var list2 = [0. ? list];assert(list2.length == 1);
Copy the code
You can determine whether an element is needed directly within the element:
var promoActive = true;
var nav = [
'Home'.'Furniture'.'Plants',
promoActive? 'About':'Outlet'
];
Copy the code
Even use for to dynamically add multiple elements:
var listOfInts = [1.2.3];
var listOfStrings = [
'# 0'.for (var i in listOfInts) '#$i'
];
assert(listOfStrings[1] = ='# 1');
Copy the code
This dynamic capability makes Flutter very convenient when building Widget trees.
Sets
The semantics of Set are the same as those of other languages, meaning that the object in the container is unique. In Dart, Set defaults to the LinkedHashSet implementation, indicating that elements are sorted in the order they were added.
Declare the Set object:
var halogens = {'fluorine'.'chlorine'.'bromine'.'iodine'.'astatine'};
Copy the code
Iterate over Set, iterate over except for the above mentioned for… In, you can also use the forEach form of the Java-like lambada:
halogens.add('bromine');
halogens.add('astatine');
halogens.forEach((el) => print(el));
Copy the code
Output result:
fluorine
chlorine
bromine
iodine
astatine
Copy the code
Except for the object uniqueness of the container, it is basically the same as a List.
// Add a type declaration:
var elements = <String> {};var promoActive = true;
// Add elements dynamically
final navSet = {'Home'.'Furniture', promoActive? 'About':'Outlet'};
Copy the code
Maps
Map objects are declared in JavaScript fashion, and the default implementation of a Map in Dart is LinkedHashMap, which represents elements in the order in which they were added.
var gifts = {
// Key: Value
'first': 'partridge'.'second': 'turtledoves'.'fifth': 'golden rings'
};
assert(gifts['first'] = ='partridge');
Copy the code
Add a key-value pair:
gifts['fourth'] = 'calling birds';
Copy the code
Through the Map:
gifts.forEach((key,value) => print('key: $key, value: $value'));
Copy the code
function
In Dart, the Function itself is also an object, and its corresponding type is Function, which means that functions can be passed as variable values or parameter values as a method.
void sayHello(var name){
print('hello, $name');
}
void callHello(Function func, var name){
func(name);
}
void main(){
// Function variable
var helloFuc = sayHello;
// Call the function
helloFuc('Girl');
// Function arguments
callHello(helloFuc,'Boy');
}
Copy the code
Output:
hello, Girl
hello, Boy
Copy the code
For simple functions with only one expression, you can also make the function more concise by using =>, => expr is equivalent to {return expr; }, let’s look at the following statement:
String hello(var name ) => 'hello, $name';
Copy the code
Is equivalent to:
String hello(var name ){
return 'hello, $name';
}
Copy the code
parameter
Named parameters can be found everywhere in the Flutter UI library. Here is an example of using Named parameters:
void enableFlags({bool bold, boolhidden}) {... }Copy the code
Call this function:
enableFlags(bold: false);
enableFlags(hidden: false);
enableFlags(bold: true, hidden: false);
Copy the code
Named parameters are optional by default. If you want to indicate that the parameter must be passed, use @required:
void enableFlags({bool bold, @required bool hidden}) {}
Copy the code
Of course, Dart also supports generic functional forms:
void enableFlags(bool bold, bool hidden) {}
Copy the code
Unlike named arguments, arguments of this form are passed by default:
enableFlags(false.true);
Copy the code
You can use [] to add non-mandatory arguments:
void enableFlags(bool bold, bool hidden, [bool option]) {}
Copy the code
In addition, the Dart function also supports setting parameter defaults:
void enableFlags({bool bold = false.bool hidden = false{...}) }String say(String from, [String device = 'carrier pigeon'.String mood]) {}
Copy the code
Anonymous functions
By definition, an anonymous function is a function that has no defined name. If you’re familiar with this, we’ve already used it when iterating through lists and maps. Anonymous functions can simplify code even further:
var list = ['apples'.'bananas'.'oranges'];
list.forEach((item) {
print('${list.indexOf(item)}: $item');
});
Copy the code
closure
Dart supports closures. For those of you who are not familiar with JavaScript, closures may be unfamiliar. Here’s a quick explanation of closures.
Closures are tricky to define, so instead of trying to define them, we’ll try to understand them with an example:
Function closureFunc() {
var name = "Flutter"; // Name is a local variable created by init
void displayName() { // displayName() is an inner function, a closure
print(name); // Use a variable declared in the parent function
}
return displayName;
}
void main(){
MyFunc is a displayName function
var myFunc = closureFunc(); / / (1)
// Execute the displayName function
myFunc(); / / (2)
}
Copy the code
The result was to print Flutter as we expected.
After (1), name is a local variable to a function, and the referenced object should be recycled. But when we call an external name from an inner function, it still magically gets called. Why?
This is because Dart runs internal functions in a closure that is a combination of the function and the lexical environment that created the function, which contains all the local variables that the closure was created to access.
Let’s change the code briefly:
Function closureFunc() {
var name = "Flutter"; // Name is a local variable created by init
void displayName() { // displayName() is an inner function, a closure
print(name); // Use a variable declared in the parent function
}
name = 'Dart'; // reassign
return displayName;
}
Copy the code
The resulting output is Dart, and you can see that the inner function accesses the variables of the outer function in the same lexical context.
The return value
In Dart, all functions must return a value, and if they do not, null is automatically returned:
foo() {}
assert(foo() == null);
Copy the code
Process control
This part is like most languages, so I’ll just go through it briefly here.
if-else
if(hasHause && hasCar){
marry();
}else if(isHandsome){
date();
}else{
pass();
}
Copy the code
cycle
All kinds of for:
var list = [1.2.3];
for(var i = 0; i ! = list.length; i++){}for(var i in list){}
Copy the code
While and loop interrupts (interrupts also work in for) :
var i = 0;
while(i ! = list.length){if(i % 2= =0) {continue;
}
print(list[i]);
}
i = 0;
do{
print(list[i]);
if(i == 5) {break; }}while(i ! = list.length);Copy the code
If the object is of type Iterable, you can also do something like a Java lambada:
list.forEach((i) => print(i));
list.where((i) =>i % 2= =0).forEach((i) => print(i));
Copy the code
switch
Switch can be used with types such as int, double, String, and enum. Switch can only be compared with objects of the same type.
var color = ' ';
switch(color){
case "RED":
break;
case "BLUE":
break;
default:}Copy the code
assert
In Dart, the assert statement is often used to check parameters. Its full expression is assert(condition, optionalMessage). If condition is false, an [AssertionError] exception will be raised to stop executing the program.
assert(text ! =null);
assert(urlString.startsWith('https'), 'URL ($urlString) should start with "https".');
Copy the code
Assert is usually only used in the development phase and is often ignored in the production runtime environment. Assert is turned on in the following scenario:
- The Flutter
debug mode
. - Some development tools like
dartdevc
It is enabled by default. - Some tools, like
dart
和dart2js
, can be through the parameters--enable-asserts
Open it.
Exception handling
Dart’s exception handling is similar to Java’s, but all exceptions in Dart are unchecked, which means you’re not forced to handle exceptions as you are in Java.
Dart provides both Exception and Error types of exceptions. In general, you should not catch errors of the Error type, but rather try to avoid them.
For example, OutOfMemoryError, StackOverflowError, and NoSuchMethodError are all errors of the Error type.
Mentioned above, because the Dart is not like Java can declare compile-time exception, this approach can make the code more concise, but easy to ignore the exception handling, so we at the time of coding, there may be exceptions in place should pay attention to read the API documentation, in addition to write their own way, if there is exception is thrown, to make a statement in the comments. For example, one of the methods in the File class is commented:
/** * Synchronously readthe entire file contents as alist of bytes. * * Throwsa [FileSystemException] if theoperation fails. */
Uint8List readAsBytesSync();
Copy the code
An exception is thrown
throw FormatException('Expected at least 1 section');
Copy the code
In addition to throwing exception objects, throw can throw objects of any type, but it is recommended to use standard exception classes as a best practice.
throw 'Out of llamas! ';
Copy the code
Catch exceptions
You can specify the exception type with the ON keyword:
var file = File("1.txt");
try{
file.readAsStringSync();
} on FileSystemException {
//do something
}
Copy the code
Get the exception object using the catch keyword, which takes two parameters, the first being the exception object and the second the error stack.
try{
file.readAsStringSync();
} on FileSystemException catch (e){
print('exception: $e');
} catch(e, s){ // Other types
print('Exception details:\n $e');
print('Stack trace:\n $s');
}
Copy the code
Throw to the upper level using rethrow:
try{
file.readAsStringSync();
} on FileSystemException catch (e){
print('exception: $e');
} catch(e){
rethrow;
}
Copy the code
finally
Finally is used for operations such as releasing resources. It means that it must be executed eventually, even if a try… A catch has a return, and the code inside it promises to execute.
try{
print('hello');
return;
} catch(e){
rethrow;
} finally{
print('finally');
}
Copy the code
Output:
hello
finally
Copy the code
class
Dart is an object-oriented programming language. All objects are instances of a class that extends from the Object class.
A simple class:
class Point {
num x, y;
/ / the constructor
Point(this.x, this.y);
// Instance method
num distanceTo(Point other) {
var dx = x - other.x;
var dy = y - other.y;
returnsqrt(dx * dx + dy * dy); }}Copy the code
Members of the class
The Dart through. To call class member variables and methods.
// Create an object, the new keyword can be omitted
var p = Point(2.2);
// Set the value of the instance variable y.
p.y = 3;
// Get the value of y.
assert(p.y == 3);
// Invoke distanceTo() on p.
num distance = p.distanceTo(Point(4.4));
Copy the code
You can also pass…? To avoid null objects. One of the criticisms of Java is that a lot of nullPonterExceptions are often needed to avoid nullPonterExceptions. In Dart, this problem can be easily avoided:
// If p is non-null, set its y value to 4.p? .y =4;
Copy the code
Dart does not use the keywords private, protected, or public. If you want to declare a variable private, you add an underscore underscore (_) before the variable name. The declared variable is private and visible only in the library.
class Point{
num _x;
num _y;
}
Copy the code
Constructor
If no constructor is declared, Dart generates a default constructor with no arguments for the class, declaring a constructor with arguments, as Java does:
class Person{
String name;
int sex;
Person(String name, int sex){
this.name = name;
this.sex = sex; }}Copy the code
You can also use the simplified version:
Person(this.name, this.sex);
Copy the code
Or named constructor:
Person.badGirl(){
this.name = 'Bad Girl';
this.sex = 1;
}
Copy the code
You can also create instances with the factory keyword:
Person.goodGirl(){
this.name = 'good Girl';
this.sex = 1;
}
factory Person(int type){
return type == 1 ? Person.badGirl(): Person.goodGirl();
}
Copy the code
Factory corresponds to the language-level implementation of the factory pattern in Design pattern and has a number of applications in Flutter’s library, such as Map:
// Some code
abstract class Map<K.V> {
factory Map.from(Map other) = LinkedHashMap<K, V>.from;
}
Copy the code
If the creation of an object is complex, such as choosing a different subclass to implement or caching instances, you can use this method. In the Map example above, we subclass LinkedHashMap by declaring factory (linkedhashmap. from is also a factory, which contains the creation process).
If you want to do something before the object is created, such as checking parameters, you can use the following methods:
Person(this.name, this.sex): assert(sex == 1)
Copy the code
Some simple operations added after the constructor are called the Initializer List.
In Dart, the initialization sequence is as follows:
- Initializer list;
- Constructor that executes the parent class;
- Constructor to execute a subclass.
class Person{
String name;
int sex;
Person(this.sex): name = 'a'.assert(sex == 1) {this.name = 'b';
print('Person'); }}class Man extends Person{
Man(): super(1) {this.name = 'c';
print('Man'); }}void main(){
Person person = Man();
print('name : ${person.name}');
}
Copy the code
The output of the code above is:
Person
Man
name : c
Copy the code
If the subclass constructor does not explicitly call the parent constructor, the parent’s default no-argument constructor is called by default. Explicitly calling the parent class’s constructor:
Man(height): this.height = height, super(1);
Copy the code
Redirect constructor:
Man(this.height, this.age): assert(height > 0), assert(age > 0);
Man.old(): this(12.60); // Call the constructor above
Copy the code
Getter and Setter
In Dart, there are specific optimizations for Getter and Setter methods. Even if undeclared, each class variable has a get method by default, as shown in the implicit interface section.
class Rectangle {
num left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
num get right => left + width;
set right(num value) => left = value - width;
num get bottom => top + height;
set bottom(num value) => top = value - height;
}
void main() {
var rect = Rectangle(3.4.20.15);
assert(rect.left == 3);
rect.right = 12;
assert(rect.left == - 8 -);
}
Copy the code
An abstract class
Dart’s abstract classes are similar to Java’s, except that they cannot be instantiated and can declare abstract methods.
abstract class AbstractContainer {
num _width;
void updateChildren(); // An abstract method that is enforced by inheriting subclasses.
get width => this._width;
int sqrt(){
return_width * _width; }}Copy the code
Implied interface
Each class in Dart implicitly defines an interface that contains all of the class’s member variables and methods. You can re-implement the associated interface methods using the implements keyword:
class Person {
// The get method is implied
final _name;
Person(this._name);
String greet(String who) => 'Hello, $who. I am $_name.';
}
class Impostor implements Person {
// Need to be reimplemented
get _name => ' ';
// Need to be reimplemented
String greet(String who) => 'Hi $who. Do you know who I am? ';
}
Copy the code
Implement multiple interfaces:
class Point implements Comparable.Location {... }Copy the code
inheritance
Similar to Java, inheritance uses the extends keyword:
class Television {
voidturnOn() { doSomthing(); }}class SmartTelevision extends Television {
@override
void turnOn() {
super.turnOn(); // Call the parent methoddoMore(); }}Copy the code
Overloaded operator
In particular, Dart also allows overloaded operators, such as the subscript access element supported by the List class, to define the associated interface:
E operator[] (int index);
Copy the code
We further illustrate the overloaded operator with the following example:
class MyList{
var list = [1.2.3];
operator[] (int index){
returnlist[index]; }}void main() {
var list = MyList();
print(list[1]); 2 / / output
}
Copy the code
Extension methods
This feature is also one of the highlights of Dart (not supported until after Dart2.7) and can be tagged to Prototype in JavaScript. With this feature, you can even add new methods to the class library:
// Add a new method to the String class with the keyword extension
extension NumberParsing on String {
int parseInt() {
return int.parse(this); }}Copy the code
The following String can call this method:
print(The '42'.parseInt());
Copy the code
Enumerated type
Enumeration types and keep them consistent with Java keywords:
enum Color { red, green, blue }
Copy the code
Use in switch:
// color is the enmu color type
switch(color){
case Color.red:
break;
case Color.blue:
break;
case Color.green:
break;
default:
break;
}
Copy the code
The enumerated type also has a getter for index, which is a contiguous sequence of numbers starting at 0:
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
Copy the code
New feature: Mixins
This feature further enhances the ability to reuse code. If you’ve ever written Android layout XML code or Freemaker templates, this feature can be interpreted as inlclude.
Declare a mixin class:
mixin Musical {
bool canPlayPiano = false;
bool canCompose = false;
bool canConduct = false;
void entertainMe() {
if (canPlayPiano) {
print('Playing piano');
} else if (canConduct) {
print('Waving hands');
} else {
print('Humming to self'); }}}Copy the code
Reuse with the with keyword:
class Musician extends Performer with Musical {
/ /...
}
class Maestro extends Person
with Musical.Aggressive.Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true; }}Copy the code
Mixin classes can even implement inherited functionality via the ON keyword:
mixin MusicalPerformer on Musician {
/ /...
}
Copy the code
Class variables and class methods
class Queue {
/ / class variables
static int maxLength = 1000;
/ / a class constant
static const initialCapacity = 16;
/ / class methods
static void modifyMax(intmax){ _maxLength = max; }}void main() {
print(Queue.initialCapacity);
Queue.modifyMax(2);
print(Queue._maxLength);
}
Copy the code
The generic
There are two main functions of generics in object-oriented languages:
Type safety checks to kill errors at compile time:
var names = List<String> (); names.addAll(['Seth'.'Kathy'.'Lars']);
// Error compiling
names.add(42);
Copy the code
2, enhance code reuse, such as the following code:
abstract class ObjectCache {
Object getByKey(String key);
void setByKey(String key, Object value);
}
abstract class StringCache {
String getByKey(String key);
void setByKey(String key, String value);
}
Copy the code
You can combine them into a single class by generics:
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
Copy the code
In Java, generics are implemented by type erasing, but in Dart they are real generics:
var names = <String> []; names.addAll(['Tom'."Cat"]);
// is can be used for type determination
print(names is List<String>); // true
print(names is List); // true
print(names is List<int>); //false
Copy the code
You can restrict generic types with extends keyword, just like in Java:
abstract class Animal{}
class Cat extends Animal{}
class Ext<T extends Animal>{
T data;
}
void main() {
var e = Ext(); // ok
var e1 = Ext<Animal>(); // ok
var e2 = Ext<Cat>(); // ok
var e3 = Ext<int> ();// compile error
}
Copy the code
Use the class library
All living programming languages have a powerful class library behind them that allows us to stand on the shoulders of giants without having to reinvent the wheel.
The import library
In Dart, the class library is imported by importing keywords.
The built-in class library uses dart: to introduce:
import 'dart:io';
Copy the code
Learn more about the built-in libraries here.
Third-party libraries or native DART files start with package: :
For example, import dio library for network request:
import 'package:dio/dio.dart';
Copy the code
The Dart application itself is a library, for example, my application name is CCsys, importing classes from other folders:
import 'package:ccsys/common/net_utils.dart';
import 'package:ccsys/model/user.dart';
Copy the code
If you use an IDE for development, you usually don’t have to worry about this, it will automatically import for you.
Dart manages libraries through pub.dev, similar to Maven in the Java world or Node.js NPM, where you can find many useful libraries.
Resolve class name conflicts
If the imported libraries have class name conflicts, you can avoid this problem by using aliases as:
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// Use Element from lib1
Element element1 = Element(a);// Use Element from lib2
lib2.Element element2 = lib2.Element(a);Copy the code
Import partial classes
There may be many classes in a DART file, and if you want to reference only a few of them, you can add show or hide to handle this:
// File: my_lib.dart
class One {}
class Two{}
class Three{}
Copy the code
Use show to import classes One and Two:
// File: test.dart
import 'my_lib.dart' show One, Two;
void main() {
var one = One();
var two = Two();
//compile error
var three = Three();
}
Copy the code
We can also use hide to exclude Three.
// File: test.dart
import 'my_lib.dart' hide Three;
void main() {
var one = One();
var two = Two();
}
Copy the code
Lazy-loaded library
Lazy loading is currently only supported in the Web App (Dart 2JS). Flutter and Dart VM are not supported. We will give a brief introduction here.
You need to declare the lazy loading class library with deferred AS:
import 'package:greetings/hello.dart' deferred as hello;
Copy the code
When you need to use it, load it with loadLibrary() :
Future greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
Copy the code
You can call loadLibrary multiple times and it will not be loaded twice.
Asynchronous support
This is a slightly more complicated topic, and we’ll cover it in a separate article. Keep an eye out for the next one.
The resources
-
Top 10 reasons to learn Dart
-
A tour of the Dart language
-
An understanding of the Dart constant constructor
-
JavaScript closures
aboutAgileStudio
We are a team of experienced independent developers and designers with solid technical strength and years of product design and development experience, providing reliable software customization services.