Making of transferZ (Part: 1/n)
Tags: dart, flutter, android, webdev
Published: 2019-04-22 10:41:01

---
title: Making of transferZ (Part: 1/n)
published: true
description: Detailed implementation guideline of transferZ, an opensource flutter application, used for transferring files wirelessly in between devices
tags: dart, flutter, android, webdev
---

Here we begin ... 

## idea

Building a [flutter](https://flutter.dev/) application for transferring files wirelessly, in between your devices, mostly written with [Dart Language](https://www.dartlang.org/), can be really great.


### info
*transferZ* is an opensource project, find whole source code [here](https://github.com/itzmeanjan/transferZ). Feel free to contribute by submitting a PR.

Now, you can [download](https://github.com/itzmeanjan/transferZ/releases/download/v1.0/transferZ-1.0.apk) and test *transferZ* on your device.



Today we gonna implement *Client-Server* portion of this Flutter application. 

And at end of this series, you'll have a working file transfer app.

> Wanna do so ?

> Yes

> Alright follow me :wink:


## model

A device can act in either of these two modes at a time.

1. Receive
2. Send

In first case, device will behave like a *Client* and in case of later one, it'll simply be a *Server*.

When in *Receive* mode, first *Client* fetches a list of files, which are ready to be shared by *Server*.

Then it iterates through that list of files and fetches them one by one, while *Server* keeps listening for incoming requests and serves them, if it's from an allowed PEER.


Yes, it's this much simple :smile:.


## init

Let's first write some code to make our device act like a *Client*.

### Receive Mode

Following *import* statements will fetch required Dart classes for us.

```dart
import 'dart:io';
import 'dart:convert' show json, utf8;
import 'dart:async';
```

A class, named Client will incorporate all *Client* functionalities, to be shown in *Receive Mode*.

```dart
class Client {
  String _peerIP; // this the server IP, where we'll connect
  int _peerPort; // port on which server listens

  HttpClient _httpClient;

  Client(this._peerIP, this._peerPort) {
    _httpClient = HttpClient(); // gets initialized in constructor block
  }
}
```

Now add a method, named *connect* in our Client class.

```dart
// method GETs a certain path, and returns a HttpClientRequest object, which is to be closed later, to send that request to server.
Future<HttpClientRequest> connect(String path) async => await _httpClient
      .get(this._peerIP, this._peerPort, path)
      .catchError((e) => null);
```

As we modeled our *Client*, to be first fetching a list of files, which are ready to be shared by *Server*, we'll require a method, named *fetchFileNames*, in Client class.

```dart
Future<List<String>> fetchFileNames(HttpClientRequest req) async {
    // a map, stores JSON response from server
    var fileNames = <String, List<String>>{};
    // Completer class helps us to return a Future from this method and when Completer completes, result is send back.
    var completer = Completer<List<String>>();
    await req.close().then((HttpClientResponse resp) {
      if (resp.statusCode == 200)
        // in case of success, listen for data passed through Stream
        resp.listen(
          (data) => fileNames = serializeJSON(utf8.decode(data)), // decode response and then serialize it into Map<String, List<String>> 
          onDone: () => completer.complete(fileNames['files']), // extract list of file names and send that back
          onError: (e) => completer.complete(<String>[]), // send a blank list
          cancelOnError: true,
        );
      else
        // if HttpStatus other than 200, simply return a blank list
        resp.listen(
          (data) {},
          onDone: () => completer.complete(<String>[]),
          onError: (e) => completer.complete(<String>[]),
          cancelOnError: true,
        );
    });
    return completer.future;
  }
```

Closing *HttpClientRequest* object, which was received from previous *connect* method, gives us a *HttpClientResponse* object, which is used for reading response from *Server*. 


Now we need to define another method *serializeJSON*, which will help us to convert JSON response into Map < String, List< String > >.


```dart
Map<String, List<String>> serializeJSON(String data) =>
    return Map<String, dynamic>.from(json.decode(data))
        .map((key, val) => MapEntry(key, List<String>.from(val)));
```

> What do we have now ?
> A list of Strings, which are file names to be fetched from Server.

Let's fetch those files from *Server*. Add a new method *fetchFile* in *Client* class.


```dart
Future<bool> fetchFile(HttpClientRequest req, String targetPath) async {
    var completer = Completer<bool>(); // returns a future of boolean type, to denote success or failure
    await req.close().then((HttpClientResponse resp) {
      if (resp.statusCode == 200)
        File(targetPath).openWrite(mode: FileMode.write).addStream(resp).then(
              (val) => completer.complete(true),
              onError: (e) => completer.complete(false),
            ); // file opened in write mode, and added response Stream into file.
      else
        resp.listen(
          (data) {},
          onDone: () => completer.complete(false),
          onError: (e) => completer.complete(false),
          cancelOnError: true,
        ); // in case of error, simply return false to denote failure
    });
    return completer.future;
  }
```

If file is properly fetched, method returns true, else false is sent back

And a last method, named *disconnect* for closing HttpClient connection.


```dart
disconnect() =>
      // never force close a http connection, by passing force value attribute true.
      _httpClient.close();
```

And we've completed declaring client :sunglasses:.


### Send Mode

Back to *Server*. Let's first define a class, named Server.


```dart
import 'dart:io';
import 'dart:convert' show json;

class Server {
  String _host; // IP where on which server listens
  int _port; // port on which server listens for incoming connections
  List<String> _filteredPeers; // list of IP, which are permitted to request files
  List<String> _files; // files ready to be shared
  ServerStatusCallBack _serverStatusCallBack; // Server status callback, lets user know, what's happening
  bool isStopped = true; // server state denoter

  Server(this._host, this._port, this._filteredPeers, this._files,
      this._serverStatusCallBack); // constructor defined
```

For controlling *Server*, we need to declare another variable with in this class.

```dart
    HttpServer _httpServer;
```

Let's start listening for incoming connections.

```dart
initServer() async {
    await HttpServer.bind(this._host, this._port).then((HttpServer server) {
    // binds server on specified IP and port
      _httpServer = server; // initializes _httpServer, which will be useful for controlling server functionalities
      isStopped = false; // server state changed
      _serverStatusCallBack.generalUpdate('Server started'); // lets user know about it, using callback function
    }, onError: (e) {
      isStopped = true;
      _serverStatusCallBack.generalUpdate('Failed to start Server');
    });
    // listens for incoming request in asynchronous fashion
    await for (HttpRequest request in _httpServer) {
      handleRequest(request); // handles incoming request
    }
  }
```

For handling incoming connection, add a method, named *handleRequest* in our Server class.

```dart
handleRequest(HttpRequest request) {
    // first checks whether device allowed to request file or not
    if (isPeerAllowed(request.connectionInfo.remoteAddress.address)) {
      if (request.method == 'GET')
        handleGETRequest(request); // only GET is permitted
      else
        request.response
          ..statusCode = HttpStatus.methodNotAllowed
          ..headers.contentType = ContentType.json
          ..write(json.encode(
              <String, int>{'status': 'GET method only'}))
          ..close().then(
              (val) => _serverStatusCallBack.updateServerStatus({
                    request.connectionInfo.remoteAddress.host:
                        'GET method only'}), onError: (e) {
            _serverStatusCallBack.updateServerStatus({
              request.connectionInfo.remoteAddress.host:
                  'Transfer Error'
            });
          }); // otherwise let client know about it by sending a JSON response, where HTTP statusCode is set as HttpStatus.methodNotAllowed
    } else
      request.response
        ..statusCode = HttpStatus.forbidden
        ..headers.contentType = ContentType.json
        ..write(
            json.encode(<String, int>{'status': 'Access denied'}))
        ..close().then(
            (val) =>
                _serverStatusCallBack.generalUpdate('Access Denied'),
            onError: (e) {
          _serverStatusCallBack.generalUpdate('Transfer Error');
        }); // if client is not permitted to access
  }
```

Checking if Peer can be granted access, is done in *isPeerAllowed*.

```dart
bool isPeerAllowed(String remoteAddress) =>
      this._filteredPeers.contains(remoteAddress); // _filteredPeers was supplied during instantiating this class
```

Time to handle filtered GET requests, with *handleGETRequest*.

```dart
handleGETRequest(HttpRequest getRequest) {
    if (getRequest.uri.path == '/') {
    // client hits at `http://hostAddress:port/`, for fetching list of files to be shared 
      getRequest.response
        ..statusCode = HttpStatus.ok // statusCode 200
        ..headers.contentType = ContentType.json // JSON response sent
        ..write(json.encode(<String, List<String>>{"files": this._files})) // response to be processed by client for converting JSON string back to Map<String, List<String>>
        ..close().then(
            (val) => _serverStatusCallBack.updateServerStatus({
                  getRequest.connectionInfo.remoteAddress.host:
                      'Accessible file list shared'
                }), onError: (e) {
          _serverStatusCallBack.updateServerStatus({
            getRequest.connectionInfo.remoteAddress.host:
                'Transfer Error'
          }); // in case of error
        });
    } else {
      if (this._files.contains(getRequest.uri.path)) {
    // if client hits at `http://hostIP:port/filePath`, first it's checked whether this file is supposed to be shared or not
        String remote = getRequest.connectionInfo.remoteAddress.host;
        getRequest.response.statusCode = HttpStatus.ok;
        _serverStatusCallBack.updateServerStatus({
          getRequest.connectionInfo.remoteAddress.host:
              'File fetch in Progress'
        });
        // then file is opened and added into response Stream
        getRequest.response
            .addStream(File(getRequest.uri.path).openRead())
            .then(
          (val) {
            getRequest.response.close(); // close connection
            _serverStatusCallBack
                .updateServerStatus({remote: 'File fetched'});
          },
          onError: (e) {
            _serverStatusCallBack
                .updateServerStatus({remote: 'Transfer Error'}); // in case of error
          },
        );
      }
    }
  }
```

And last but not least, a way to stop *Server*.

```dart
stopServer() {
    isStopped = true;
    _httpServer?.close(force: true);
    _serverStatusCallBack.generalUpdate('Server stopped');
  }
```

And this is all about *Server* :sunglasses:.


Now define an abstract class *ServerStatusCallBack*, to facilitate callback functionality. This abstract class needs to be implemented, from where we plan to start, control and stop *Server*.


```dart
abstract class ServerStatusCallBack {
  updateServerStatus(Map<String, String> msg); // a certain peer specific message

  generalUpdate(String msg); // general message about current server state
}
```

## run

Time to run our client.

### Receive Mode

Currently I'm going to run client simply from a command line app, which is to be updated in upcoming articles, when we reach UI context in flutter.

```dart
main() {
  var client = Client('x.x.x.x', 8000); // x.x.x.x -> server IP and 8000 is port number
// feel free to change port number, try keeping it >1024
  client.connect('/').then((HttpClientRequest req) {
    if (req != null) // checks whether server is down or not
      client.fetchFileNames(req).then((List<String> targetFiles) { // first fetch list of files by sending a request in `/` path
        targetFiles.forEach((path) { // fetch files, one by one
          client.connect(path).then((HttpClientRequest req) { // request for a file, with that file's name as request path
            if (req != null) { // if server finds request eligible
              print('fetching ${getTargetFilePath(path)}');
              client
                  .fetchFile(req, getTargetFilePath(path)) // fetch file
                  .then((bool isSuccess) { // after it's done, check for transfer status
                print(isSuccess
                    ? 'Successfully downloaded file'
                    : 'Failed to download file');
                if (targetFiles.last == path) {
                  print('complete');
                  client.disconnect(); // disconnect client, when we find that all files has been successfully fetched
                }
              });
            } else
              print('Connection Failed');
          });
        });
        if (targetFiles.isEmpty) {
          print('incomplete');
          client.disconnect(); // there might be a situation when client not permitted to access files, then we get an empty list of files, it's handled here
        }
      });
    else
      print('Connection Failed'); // couldn't connect to server, may be down
  });
}

// returns path where client will store fetched files
String getTargetFilePath(String path) =>
    '/path-to-target-directory-to-store-fetched-file/${path.split('/').last}';

```

### Send Mode

In case of Server too, for time being, we'll keep it command line executable.

Let's first define a class *ServerDriver*, which implements *ServerStatusCallBack*, gets server status updates.


```dart
class ServerDriver extends ServerStatusCallBack {
  Server server;

  @override
  updateServerStatus(Map<String, String> msg) =>
    msg.forEach((key, val) =>
      print('$key -- $val')); // peer specific message, where key of Map<String, String> is peerIP

  @override
  generalUpdate(String msg) => print(msg); // general status update about server

  init() {
    server = Server(
        '0.0.0.0',
        8000,
        <String>['192.168.1.103', '192.168.1.102'], // these are allowed peers( clients ), can request for fetching files
        <String>[
          '/path-to-file/image.png',
          '/path-to-file/spidy_love.zip',
        ],
        this); // this -- because ServerStatusCallBack is implemented in this class
  }

  start() {
    server.initServer(); // starts receiving incoming requests
  }
}

```

And finally *main()* function, which creates an instance of *ServerDriver*, starts server.


```dart
main() {
  var serverDriver = ServerDriver();
  serverDriver.init(); // binds server in a speficied address and port
  serverDriver.start(); // starts accepting incoming requests
}
```

![Client Server running on a Desktop](https://thepracticaldev.s3.amazonaws.com/i/z5zgk835gm2f3pmygj48.png)


Remember, this client-server program works in Local Area Network( LAN ). Interested in making it work in WAN, give it a try :+1:.

## next

Currently we've two working programs, a client and a server, written fully in [Dart Language](https://www.dartlang.org/), which will be eventually put into a [Flutter](https://flutter.dev/) Application [*transferZ*](https://github.com/itzmeanjan/transferZ).

In next article of this series, we'll build *Peer Discovery* portion of [*transferZ*]((https://github.com/itzmeanjan/transferZ)). 


In the mean time fork this [repo](https://github.com/itzmeanjan/transferZ), and try playing around.

You may consider following me on [Twitter](https://twitter.com/meanjanry) and [GitHub](https://github.com/itzmeanjan), for more announcements.

See you soon :wink:.