Monday, July 18, 2022

Flutter: Data Types

Dart supports several data types. In this article, we will learn about some of them:

Name Examples
String “Hello world”
int 1, 5, 10, 100
double 1.5, 0.25, 10.5
num 1, 1.5, 10
bool true, false

String

The String data type holds a sequence of characters. You can create a string using single quotes ' or double quotes ".

var myString1 = 'This is a string';
var myString2 = "This is another string";

int

The int data type holds integer values no larger than 64 bits, depending on the platform. On native platforms, values can be from -263 to 263 - 1. On the web, integer values are represented as JavaScript numbers (64-bit floating-point values with no fractional part) and can be from -253 to 253 - 1.

var myNumber = 5000;

double

The double data type holds floating-point numbers.

var myDouble = 1.25;

num

The num data type can hold integers or floating-point numbers. For example:

num myNum1 = 100;
num myNum2 = 1.25;

bool

The bool data type can hold only two values: true or false:

var myBoolean = true;

Ejemplo en DartPad

Conclusion

In this section, we learn about some of the data types supported by Dart. There are many more data types like Maps, Lists, Sets, etc. But we will learn about them in the following articles of this course.

Flutter: Variables & Constants

One variable is a space in memory where we can store a value. In Dart, the way to create and initialize a variable is:

var name = 'Yayo';  

The compiler can infer that the variable name type is String. Read more about type inference. A different way to create and initialize a variable is by writing the type explicitly like:

String name = 'Yayo';

The value of variables can change like:

String name = 'Yayo';
name = 'Carlos';

If you want to create a variable whose value never changes, we can use final or const. The difference between final and const is that final can be initialized at run time and const has to be initialized at compile time, like:

const name = 'Yayo'; // const because we initialize it at compilation time.

final String lastName; // final because we initialize it at run time
lastName = 'Arellano';

Camel case

When naming the variables, the Dart team recommends using camel case.

Example

This is an example in DartPad that you can try in the browser; just press Run to see the results.

Flutter: Comments

¿What are the comments? We can say that are code snippets that will be ignored by the application and will not execute. They help us to leave notes to guide ourselves or other developers through the code.

In this article, we will see three types of comments:

  • One-line comments start with \\
  • Multiple line comments start with \* and end with *\
  • Documentation comments start with \\\

In the next code snippet, we can see the usage of each one. When we run the code, we can see the comments do not have any effect on the printed result:

In the next image we can see the usage of the TODO tag. We can see the comment in the information section. Some IDE’s like Android Studio and Visual Studio have support for the TODO tag:

Documentation comments will be shown in the documentation section of our IDE. Check the next image:

Flutter: Hello World in DartPad

DartPad is an online code editor, so we will be able to do some coding without having to download an IDE like Visual Studio or Android Studio.

Remember that DartPad is not meant to be used in production projects; it is just a tool to try simple fragments of code.

When we open DartPad we will see a screen like this:

1- This is the code editor, and we will write the code here.
2- This is the console, and the results of our code will be printed here.
3- Documentation and code information will be shown here.

DartPad allows us to run code in our browser as you can see in the next code snippet. Just press Run and you will see the classic Hello World printed in the console.

This is the end of this small DartPad tutorial. As you can see, DartPad is a handy tool when we want to test small fragments of code.

Connectivity plus. Show your users if they have internet connection

Do you have a Flutter App and you would like to show the users when they are offline, like in the next image:

Before we start, you can download the source code from Github. The source code has examples using: cubit (flutter bloc), getx, ChangeNotifier (provider), and ValueNotifier.

In this tutorial, we will only use ValueNotifier, so you will not have to add any state management package.

Requiriments

Connectivity Plus: This plugin allows Flutter apps to discover network connectivity. For example, when you are connected or disconnected from wifi. But this package will not tell us if we have or not internet connection.
RxDart: We will use it to keep the connection state and emit a new connection status.

To know if we have an internet connection, we will check this code from stackoverflow.

1- Add dependencies

Go to the file pubspec.yaml and add the following packages.

dependencies:  
  flutter:  
    sdk: flutter  
 
  rxdart: ^0.27.3  
  connectivity_plus: ^2.3.0

2- Verify if we have an internet connection.

First, we create an enum to know the current internet status.

enum ConnectionStatus {  
  online,  
  offline,  
}

Then we create a class to help us check the internet connection. Every time the network status change, this class will emit a new connection status.

class CheckInternetConnection {
  final Connectivity _connectivity = Connectivity();

  // Default will be online. This controller will help to emit new states when the connection changes.
  final _controller = BehaviorSubject.seeded(ConnectionStatus.online);
  StreamSubscription? _connectionSubscription;

  CheckInternetConnection() {
    _checkInternetConnection();
  }

  // The [ConnectionStatusValueNotifier] will subscribe to this 
  // stream and everytime the connection status change it 
  // will update it's value
  Stream<ConnectionStatus> internetStatus() {
    _connectionSubscription ??= _connectivity.onConnectivityChanged
        .listen((_) => _checkInternetConnection());
    return _controller.stream;
  }

  // Code from stackoverflow
  Future<void> _checkInternetConnection() async {
    try {
	  // Sometimes, after we connect to a network, this function will
	  // be called but the device still do not have internet connection. 
	  // This 3 seconds delay will give some time to the device to 
	  // connect to the internet in order to avoid false-positives
      await Future.delayed(const Duration(seconds: 3));
      final result = await InternetAddress.lookup('google.com');
      if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
        _controller.sink.add(ConnectionStatus.online);
      } else {
        _controller.sink.add(ConnectionStatus.offline);
      }
    } on SocketException catch (_) {
      _controller.sink.add(ConnectionStatus.offline);
    }
  }

  Future<void> close() async {
  	// Cancel subscription and close controller
    await _connectionSubscription?.cancel();
    await _controller.close();
  }
}

This class has to be initialized only one time during the lifecycle of the app. In this example, I will initialize as a global variable inside the main.dart file:

// Initialize only one time   
final internetChecker = CheckInternetConnection();  
  
void main() async {  
  runApp(  
    MyApp(),  
  );  
}

3- State management

To keep the state we are going to use ValueNotifier so we are going to create a new class:

class ConnectionStatusValueNotifier extends ValueNotifier<ConnectionStatus> {
  // Will keep a subscription to
  // the class [CheckInternetConnection]
  late StreamSubscription _connectionSubscription;  
  
  ConnectionStatusValueNotifier() : super(ConnectionStatus.online) {
    // Everytime there a new connection status is emitted
    // we will update the [value]. This will make the widget
    // to rebuild
    _connectionSubscription = internetChecker  
        .internetStatus()  
        .listen((newStatus) => value = newStatus);  
  }  
  
    
  void dispose() {  
	 _connectionSubscription.cancel();  
	 super.dispose();  
  }  
}

4- No internet connection Widget

We will create the widget that will use a ValueListenableBuilder to listen for the statuses emitted by the class ConnectionStatusValueNotifier.

class WarningWidgetValueNotifier extends StatelessWidget {
  const WarningWidgetValueNotifier({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return ValueListenableBuilder(
      valueListenable: ConnectionStatusValueNotifier(),
      builder: (context, ConnectionStatus status, child) {
        return Visibility(
          visible: status != ConnectionStatus.online,
          child: Container(
            padding: const EdgeInsets.all(16),
            height: 60,
            color: Colors.brown,
            child: Row(
              children: [
                const Icon(Icons.wifi_off),
                const SizedBox(width: 8),
                const Text('No internet connection.'),
              ],
            ),
          ),
        );
      },
    );
  }
}

5- Usage of the widget: WarningWidgetValueNotifier

To show the user there if there is no internet connection, we have to add the WarningWidgetValueNotifier widget on any screen that we would like to show the warning. For example:

class MyApp extends StatelessWidget {
  const MyApp();

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Value notifier Example'),
      ),
      body: Column(
        children: <Widget>[
          const WarningWidgetValueNotifier(),
          const Text('Abigail'),
          const Text('Alexandra'),
          const Text('Amelia'),
        ],
      ),
    );
  }
}

Conclusion

Showing to the users of your app if they are offline is very easy to implement. We did not even have to add complex packages or state management dependencies.

I made a video (in spanish) to show the end result:

How to format code snippets in blogger posts (May 2022)

If you want to show code snippets in your blog like this one:

var edad = 17;

  if (edad >= 18) {
    print('Es mayor de edad');
  } else {
    print('Es menor de edad');
  }

You can use highlight.js. To add it to your blog, you must edit the HTML:

Once you are inside the HTML editor add the next lines of code:

<link href='https://unpkg.com/@highlightjs/cdn-assets@11.0.1/styles/default.min.css' rel='stylesheet'/>
<script src='https://unpkg.com/@highlightjs/cdn-assets@11.0.1/highlight.min.js'/>
<script>
hljs.configure({cssSelector: &quot;code&quot;});
hljs.highlightAll();
</script>

The previous code must be added before the <head> tag. Check the following image:

Now the code snippets that you want to highlight must be inside the tags <pre><code></code></pre>. If you want to write inline code snippets, use the tag <code></code>.

Example: The code at the beginning of this post has to be written like this::

<pre><code>
var edad = 17;
if (edad >= 18) {
	print('Es mayor de edad');
} else {
	print('Es menor de edad');
}
</code></pre>

It will look like this in the editor:

Thanks for reading this article. I hope it can help you to improve your blogs.

How to create a Google Chrome extension with Flutter

We are going to learn how to create a Google Chrome extension with Flutter. The Flutter version in this article is Flutter 2.2.3 and has been tested also in 2.10.5. The extension will look like this:

Steps

We will start with the default project, the counter app we all know.

First, we will open the file index.html and search for the tag <script> because we will delete all the content inside it. We do not need this script because we do not need PWA functionality.

After deleting the previous code, we will add a new script in the <body> tag. It will look like this:

<body>

<script src="main.dart.js" type="application/javascript"></script>

</body>

Now we are going to replace the content of the manifest.json with the following JSON:

{
  "name": "flutter_chrome_extension",
  "description": "flutter_chrome_extension",
  "version": "1.0.0",
  "content_security_policy": {
    "extension_pages": "script-src 'self' ; object-src 'self'"
  },
  "action": {
    "default_popup": "index.html",
    "default_icon": "/icons/Icon-192.png"
  },
  "manifest_version": 3
}

Note: If you want to know more about the manifest.json, you can visit this website: Welcome to manifest 3.

Now we are going to create the web app with the next command:

flutter build web --web-renderer html --csp

Then we will open Chrome and go to chrome://extensions/ we will click Load unpacked and select the route ../build/web/. This route was generated in the previous step.

We can see that the extension was added successfully.

But there is a problem, if we click the extension we can only see a very small square:

We go back to the file index.html and we add the size in the <html> tag:

<html style="height: 600px; width: 350px">

We generate the web app again and we reload the extension. Now it works correctly:

Conclusion

With Flutter, it is straightforward to create Google Chrome extensions. You can find the source code of this article on Github

You can watch the video tutorial in youtube and remember to activate the subtitles in english:

Embed an http web server in Android

Do you want to embed an HTTP web server in Android to create Rest Api’s (GET, POST, PUT, DELETE, etc.)? This is the right place. In this article, we are going to learn how to do it. The tools that we are going to use are:

  • Kotlin
  • Ktor to create the web server
  • Koin to do dependency injection

The architecture of our project will look like this:

We divide the code into three sections:

Controller: Contains the Rest Api’s and is the entry point for the request made by the client.
Service Layer: Contains all the business logic.
Repository: Here, we read & write data to the database

Important: In this tutorial, we will not create a real database. We will store the data in a List inside the class UserRepository. The reason is that I want to keep this tutorial simple.

Dependencies

We start by adding all the dependencies we will need in this project to the build.gradle file.

 // Ktor
def ktor_version = "1.6.1"
implementation "io.ktor:ktor:$ktor_version"
implementation "io.ktor:ktor-server-netty:$ktor_version"
implementation "io.ktor:ktor-gson:$ktor_version"

// Dependency Injection
implementation "io.insert-koin:koin-ktor:3.1.2"

// Test Ktor
testImplementation "io.ktor:ktor-server-tests:$ktor_version"
testImplementation "io.mockk:mockk:1.12.0"

Models

These classes will help us to represent the objects inside our project. First, we have the User class:

data class User(
    val id: Int? = null,
    val name: String? = null,
    val age: Int? = null
)

Also, we have a class that I will call ResponseBase. This class will help us to build the JSON response for each request made by the client

data class ResponseBase<T>(
    val status: Int = 0,
    val data: T? = null,
    val message: String = "Success"
)

If you look carefully, you will notice that the class ResponseBase will create a JSON that will look like this:

{
    "message": "Success",
    "status": 999,
    "data": "La informaciĆ³n en data puede cambiar"
}

Repository

Usually, in this layer, we will read and write into the database, but in this tutorial, we will not create a database. All the data will be stored inside a List.

We start by creating an interface, UserRepository. It will help abstract the repository’s implementation and make our code easier to test.

interface UserRepository {
    fun personList(): ArrayList<User>
    fun addPerson(user: User): User
    fun removePerson(id: Int): User
}

Then we create the class UserRepositoryImp. In this class, we will implement the interface’s functions.

class UserRepositoryImp : UserRepository {
    private var idCount = 0;
    private val userList = ArrayList<User>()

    override fun userList(): ArrayList<User> = userList

    override fun addUser(user: User): User {
        val newUser = user.copy(id = ++idCount);
        userList.add(newUser)
        return newUser
    }

    override fun removeUser(id: Int): User {
        userList.find { it.id == id }?.let {
            userList.remove(it)
            return it
        }
        throw GeneralException("Cannot remove user: $id")
    }
}

Some things to notice are:

  • We have a counter idCount that will increase each time we add a user. This counter is the ID of a new user.
  • We store all the users in the list userList.
  • When we want to delete a user, and we cannot find him, we will throw a GeneralException that we will look at in detail later.

Service Layer

In this layer, we will write our web server’s business logic. In this tutorial, we will only check if the user’s name and age are correct. If they are not right, we will throw an exception.

class UserService : KoinComponent {

    private val userRepository by inject<UserRepository>()

    fun userList(): List<User> = userRepository.userList()

    fun addUser(user: User): User {
        if (user.name == null)
            throw MissingParamsException("name")
        if (user.age == null)
            throw MissingParamsException("age")
        if (user.age < 0)
            throw GeneralException("Age cannot be negative number")
        return userRepository.addUser(user)
    }

    fun removeUser(id: Int): User = userRepository.removeUser(id)
}

Also, we can see that we are using dependency injection with Koin to add UserRepository, so our class must implement KoinComponent.

Controller

In this section we will write the code to create the Rest Api’s:

fun Route.userController() {
    val userService by inject<UserService>()

    get("/user") {
        call.respond(ResponseBase(data = userService.userList()))
    }

    post("/user") {
        val person = call.receive<User>()
        call.respond(ResponseBase(data = userService.addUser(person)))
    }

    delete("/user/{id}") {
        val id = call.parameters["id"]?.toInt()!! // Force just for this example
        call.respond(ResponseBase(data = userService.removeUser(id)))
    }
}

We just created three Api 's GET, POST & DELETE. This class depends on UserService, so we use Koin to inject it. As you can see, the response we send to the client is made in the function call.respond(), and this response is always of the type ResponseBase. The only thing that changes is the property ResponseBase.data.

Exception handling

Because we want to throw custom exceptions wherever we are in our code we can use the plugin StatusPages. Now everytime we throw an exception we can catch it and send an appropriate response to the client. We create a file CustomExceptions and the content will be:

val handleException: Application.() -> Unit = {
    install(StatusPages) {
        exception<CustomExceptions> {
            call.respond(ResponseBase(it.status, null, it.description))
        }
        exception<Throwable> {
            it.printStackTrace()
            call.respond(ResponseBase(9999, null, "Unknown error"))
        }
    }
}

open class CustomExceptions(val status: Int, val description: String) : Exception(description)

class MissingParamsException(param: String) : CustomExceptions(100, "Missing parameter: $param")
class GeneralException(description: String) : CustomExceptions(999, description)

We have 2 custom exceptions MissingParamsException & GeneralException both extends from CustomExceptions. Now when we throw an exception the next code will handle it and will send the appropriate response to the client:

    install(StatusPages) {
        exception<CustomExceptions> {
            call.respond(ResponseBase(it.status, null, it.description))
        }
        exception<Throwable> {
            it.printStackTrace()
            call.respond(ResponseBase(9999, null, "Unknown error"))
        }
    }

Initialize the server

We will create the server inside an Android Service and start the service every time the device boots. We create a file HttpService with the next code:

const val PORT = 8080

class HttpService : Service() {
    override fun onCreate() {
        super.onCreate()
        Thread {
            InternalLoggerFactory.setDefaultFactory(JdkLoggerFactory.INSTANCE)
            embeddedServer(Netty, PORT) {
                install(ContentNegotiation) { gson {} }
                handleException()
                install(Koin) {
                    modules(
                        module {
                            single<UserRepository> { UserRepositoryImp() }
                            single { UserService() }
                        }
                    )
                }
                install(Routing) {
                    userController()
                }
            }.start(wait = true)
        }.start()
    }

    override fun onBind(intent: Intent): IBinder? = null
}

class BootCompletedReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
            Log.d("BootCompletedReceiver", "starting service HttpService...")
            context.startService(Intent(context, HttpService::class.java))
        }
    }
}

Android Manifest.

We update the manifest to add the permissions, services & receivers. After updating the manifest will look like:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.nopalsoft.http.server">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AndroidHttpServer">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".server.HttpService"
            android:enabled="true" />

        <receiver android:name=".server.BootCompletedReceiver">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="android.intent.action.QUICKBOOT_POWERON" />
            </intent-filter>
        </receiver>
    </application>

</manifest>

Final steps

If you want to start the service as soon as the app is launched. You can start the service in the MainActivity like this:

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        startService(Intent(this, HttpService::class.java))
 }

If you want to test with postman and the Android Emulator, you must forward the requests. Look at this answer in stackoverflow to get more information.

Postman

We use postman to call our Rest Api’s, and you can see the results in the following images:

Unit testing

Finally, these two classes will help you to understand how to add unit tests to the code. Use the ./gradlew clean test --info command to run the unit tests.

Class BaseModuleTest:

abstract class BaseModuleTest {

    private val gson = Gson()
    protected var koinModules: Module? = null
    protected var moduleList: Application.() -> Unit = { }

    init {
        stopKoin()
    }

    fun <R> withBaseTestApplication(test: TestApplicationEngine.() -> R) {
        withTestApplication({
            install(ContentNegotiation) { gson { } }
            handleException()
            koinModules?.let {
                install(Koin) {
                    modules(it)
                }
            }
            moduleList()
        }) {
            test()
        }
    }

    fun toJsonBody(obj: Any): String = gson.toJson(obj)

    fun <T> TestApplicationResponse.parseBody(clazz: Class<T>): ResponseBase<T> {
        val typeOfT: Type = TypeToken.getParameterized(ResponseBase::class.java, clazz).type
        return gson.fromJson(content, typeOfT)
    }

    fun <T> TestApplicationResponse.parseListBody(clazz: Class<T>): ResponseBase<List<T>> {
        val typeList = TypeToken.getParameterized(List::class.java, clazz).type
        val typeOfT: Type = TypeToken.getParameterized(ResponseBase::class.java, typeList).type
        return gson.fromJson(content, typeOfT)
    }
}

Class UserModuleTest:

class UserModuleTest : BaseModuleTest() {
    private val userRepositoryMock: UserRepository = mockk()

    init {
        koinModules = module {
            single { userRepositoryMock }
            single { UserService() }
        }
        moduleList = {
            install(Routing) {
                userController()
            }
        }
    }

    @Test
    fun `Get users return successfully`() = withBaseTestApplication {
        coEvery { userRepositoryMock.userList() } returns arrayListOf(User(1, "Yayo", 28))

        val call = handleRequest(HttpMethod.Get, "/user")

        val response = call.response.parseListBody(User::class.java)

        assertEquals(call.response.status(), HttpStatusCode.OK)
        assertEquals(response.data?.get(0)?.name, "Yayo")
        assertEquals(response.data?.get(0)?.age, 28)
    }

    @Test
    fun `Missing name parameter`() = withBaseTestApplication {
        val call = handleRequest(HttpMethod.Post, "/user") {
            addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
            setBody(toJsonBody(User(age = 27)))
        }
        val response = call.response.parseBody(User::class.java)

        assertEquals(call.response.status(), HttpStatusCode.OK)
        assertEquals(response.data, null)
        assertEquals(response.status, 100)
        assertEquals(response.message.contains("name"), true)
    }

    @Test
    fun `Missing age parameter`() = withBaseTestApplication {
        val call = handleRequest(HttpMethod.Post, "/user") {
            addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
            setBody(toJsonBody(User(name = "Yayo")))
        }
        val response = call.response.parseBody(User::class.java)

        assertEquals(call.response.status(), HttpStatusCode.OK)
        assertEquals(response.data, null)
        assertEquals(response.status, 100)
        assertEquals(response.message.contains("age"), true)
    }

    @Test
    fun `Age under zero error`() = withBaseTestApplication {
        val call = handleRequest(HttpMethod.Post, "/user") {
            addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
            setBody(toJsonBody(User(name = "Yayo", age = -5)))
        }
        val response = call.response.parseBody(User::class.java)

        assertEquals(call.response.status(), HttpStatusCode.OK)
        assertEquals(response.data, null)
        assertEquals(response.status, 999)
        assertEquals(response.message.contains("Age cannot be negative number"), true)
    }
}

Source code and video tutorial

Source code can be found in github and remember there is also a video tutorial of this article in youtube (in spanish):

Entradas populares