# #1 - Whatsapp Opener Flutter App

## Idea

Enter a phone number with the country code and press a button to open the Whatsapp app or web.

## Techstack

* Flutter
    
* Packages
    
    * Flutter bloc
        
    * Freezed
        
* VSCode
    

## Let's code

### Initial project setup

```bash
flutter create whatsapp_opener
```

Remove all the comments from `pubspec.yaml` and `main.dart` files.

> Hint: If you are using VSCode, use the [Remove Comments](https://marketplace.visualstudio.com/items?itemName=plibither8.remove-comments) extension.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675472110327/f2b3c913-93aa-4f47-92e4-5174811c4fee.png align="center")

Update analysis options to maintain a clean flutter code

%[https://gist.github.com/NirmalAriyathilake/1daa3f3944acc7023ffe82191f4d04c9] 

### Folder structure

* Application - BLOC notifiers
    
* Presentation - Pages and Widgets
    

```dart
// lib/main.dart

import 'package:flutter/material.dart';

import 'presentation/app.dart';

void main() {
  runApp(const App());
}
```

```dart
// lib/presentation/app.dart

import 'package:flutter/material.dart';

import 'presentation/app.dart';

void main() {
  runApp(const App());
}
```

```dart
// lib/presentation/home_page.dart

import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return  Scaffold(
      appBar: AppBar(),
    );
  }
}
```

### Now let's create the UI for the home page

* A text field to enter a number
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675558695997/bae919d5-04a6-4695-a2d8-ec6db3c78d1b.png align="center")
    

```dart
// lib/presentation/home_page.dart

import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Whatsapp Opener"),
        centerTitle: true,
      ),
      body: Container(
        padding: const EdgeInsets.symmetric(
          horizontal: 10,
          vertical: 5,
        ),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            TextField(
              decoration: InputDecoration(labelText: 'Enter number'),
            ),
            ElevatedButton(
              onPressed: () {},
              child: const Text("Open Whatsapp"),
            )
          ],
        ),
      ),
    );
  }
}
```

### Now let's create the logic

The main use case flow is,

1. The user enters a number into a `Text Field`
    
2. Then press the button
    
3. Whatsapp opens with a chat for the entered number
    

To implement application logic I'm using the [flutter\_bloc](https://pub.dev/packages/flutter_bloc) package. Using that I'm going to,

1. Hold the user entered number
    
2. Use that number to open the chat when the user presses the button
    

> Hint: Install the [bloc](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc) extension to use flutter\_bloc easily

To generate boilerplate codes and manage classes easily I'm using the [freezed](https://pub.dev/packages/freezed) package with [freezed\_annotation](https://pub.dev/packages/freezed_annotation) and [build\_runner](https://pub.dev/packages/build_runner) packages.

```yaml
// pubspec.yaml

name: whatsapp_opener
description: Open whatsapp chat for any whatsapp user without saving the number

publish_to: 'none'

version: 1.0.0+1

environment:
  sdk: '>=2.19.0 <3.0.0'

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.2
  flutter_bloc: ^8.1.1
  freezed_annotation: ^2.2.0

dev_dependencies:
  flutter_test:
    sdk: flutter

  flutter_lints: ^2.0.0
  build_runner: ^2.3.3
  freezed: ^2.3.2

flutter:
  uses-material-design: true
```

Now right-click on the application folder and select the `Cubit: New cubit` option. It will give a prompt to enter the notifier name. I'm calling this `AppActorNotifier`.

After entering the name for the cubit, the bloc extension will create 2 files.

```dart
// lib/application/cubit/app_actor_notifier_cubit.dart

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'app_actor_notifier_state.dart';
part 'app_actor_notifier_cubit.freezed.dart';

class AppActorNotifierCubit extends Cubit<AppActorNotifierState> {
  AppActorNotifierCubit() : super(AppActorNotifierState.initial());
}

// lib/application/cubit/app_actor_notifier_state.dart

part of 'app_actor_notifier_cubit.dart';

@freezed
class AppActorNotifierState with _$AppActorNotifierState {
  const factory AppActorNotifierState.initial() = _Initial;
}
```

Now you can see `part 'app_actor_notifier_cubit.freezed.dart';` this line gives an error. Because that file doesn't exist yet. To generate that file run the build\_runner.

```bash
flutter pub run build_runner watch --delete-conflicting-outputs
```

> Note :
> 
> * watch : Build\_runner is going to keep watching for any changes and re-run itself to update the generated files
>     
> * \--delete-conflicting-outputs : Replace the old generated files with the newer ones
>     

Now let's update the state class to hold the number input. Also, I'm renaming the cubit folder to app\_actor\_notifier.

```dart
// lib/application/app_actor_notifier/app_actor_notifier_state.dart

part of 'app_actor_notifier_cubit.dart';

@freezed
class AppActorNotifierState with _$AppActorNotifierState{
  const factory AppActorNotifierState({
    required String number,
  }) = _AppActorNotifierState;

  factory AppActorNotifierState.initial() => const AppActorNotifierState(
    number: '',
  );
}
```

Now let's create a method in the cubit to update the number value in the state when the user enters it.

```dart
// lib/application/app_actor_notifier/app_actor_notifier_cubit.dart

void onNumberChanged(String value) {
    emit(state.copyWith(number: value));
}
```

> Note :
> 
> * emit : emits the newest state
>     
> * copyWith : Generated by freezed package to clone and create a new object with the updated values without changing other values in the object.
>     

Now let's create the method to open the WhatsApp chat app with the number.

For this method, I'm using the [url\_launcher](https://pub.dev/packages/url_launcher) package.

```dart
// lib/application/app_actor_notifier/app_actor_notifier_cubit.dart

void openWhatsappChat() {
    if (state.number.isNotEmpty) {
      launchUrlString('https://wa.me/${state.number}');
    }
}
```

> Note:
> 
> * [https://wa.me](https://wa.me) : Short url to open a whatsapp chat with a given number
>     
> * state.number : Get number value from the state
>     

> Hint: After installing a plugin make sure to rebuild your app ( Not hot restart) . Platform-specific codes from plugins only get included in the build time.  Otherwise, you'll get an ***Unimplemented*** ***Error.***

Finally, we have,

```dart
// lib/application/app_actor_notifier/app_actor_notifier_cubit.dart

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:url_launcher/url_launcher_string.dart';

part 'app_actor_notifier_state.dart';
part 'app_actor_notifier_cubit.freezed.dart';

class AppActorNotifierCubit extends Cubit<AppActorNotifierState> {
  AppActorNotifierCubit() : super(AppActorNotifierState.initial());

  void onNumberChanged(String value) {
    emit(state.copyWith(number: value));
  }

  void openWhatsappChat() {
    if (state.number.isNotEmpty) {
      launchUrlString('https://wa.me/${state.number}');
    }
  }
}
```

---

Now let's use the `AppActorNotifier`,

First, we have to provide an instance of the `AppActorNotifierCubit` to our app.

```dart
// lib/presentation/app.dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:whatsapp_opener/application/app_actor_notifier/app_actor_notifier_cubit.dart';

import 'home_page.dart';

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => AppActorNotifierCubit(),
      child: MaterialApp(
        debugShowCheckedModeBanner: false,
        title: 'Whatsapp Opener',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const HomePage(),
      ),
    );
  }
}
```

Now we can bind our actor notifier to the home page.

```dart
// lib/presentation/home_page.dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:whatsapp_opener/application/app_actor_notifier/app_actor_notifier_cubit.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Whatsapp Opener"),
        centerTitle: true,
      ),
      body: Container(
        padding: const EdgeInsets.symmetric(
          horizontal: 10,
          vertical: 5,
        ),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            TextField(
              decoration: const InputDecoration(labelText: 'Enter number'),
              onChanged: (value) {
                if (value.trim().isNotEmpty) {
                  context.read<AppActorNotifierCubit>().onNumberChanged(value);
                }
              },
            ),
            ElevatedButton(
              onPressed: () {
                context.read<AppActorNotifierCubit>().openWhatsappChat();
              },
              child: const Text("Open Whatsapp"),
            )
          ],
        ),
      ),
    );
  }
}
```

> Note :
> 
> * context.read : Equal to BlocProvider.of(context) which looks up the closest ancestor instance of the specified type. Use to call events. [Learn more](https://bloclibrary.dev/#/flutterbloccoreconcepts?id=contextread).
>     

### Validations

* If the user enters an invalid number app should show an error
    
* Submit button should stay disabled until the user enters a valid number
    

```dart
// lib/application/app_actor_notifier/app_actor_notifier_state.dart

part of 'app_actor_notifier_cubit.dart';

@freezed
class AppActorNotifierState with _$AppActorNotifierState{
  const factory AppActorNotifierState({
    required String number,
    required String error,
  }) = _AppActorNotifierState;

  factory AppActorNotifierState.initial() => const AppActorNotifierState(
    number: '',
    error: '',
  );
}
```

```dart
// lib/application/app_actor_notifier/app_actor_notifier_cubit.dart

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:url_launcher/url_launcher_string.dart';

part 'app_actor_notifier_state.dart';
part 'app_actor_notifier_cubit.freezed.dart';

class AppActorNotifierCubit extends Cubit<AppActorNotifierState> {
  AppActorNotifierCubit() : super(AppActorNotifierState.initial());

  void onNumberChanged(String value) {
    if (value.contains(RegExp(r"^\+[\d\s]{2,}$"))) {
      final valueWithoutSpaces = value.replaceAll(RegExp(r"\s+"), "");

      emit(state.copyWith(
        number: valueWithoutSpaces,
        error: '',
      ));
    } else {
      emit(state.copyWith(
        error: 'Please enter a valid number',
      ));
    }
  }

  void openWhatsappChat() {
    if (state.number.isNotEmpty) {
      launchUrlString('https://wa.me/${state.number}');
    }
  }
}
```

```dart
// lib/presentation/home_page.dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import '../application/app_actor_notifier/app_actor_notifier_cubit.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Whatsapp Opener"),
        centerTitle: true,
      ),
      body: Container(
        padding: const EdgeInsets.symmetric(
          horizontal: 10,
          vertical: 5,
        ),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            TextField(
              decoration: InputDecoration(
                labelText: 'Enter number',
                errorText: context
                    .select((AppActorNotifierCubit cubit) => cubit.state.error),
              ),
              onChanged: (value) {
                if (value.trim().isNotEmpty) {
                  context.read<AppActorNotifierCubit>().onNumberChanged(value);
                }
              },
            ),
            ElevatedButton(
              onPressed: context.select((AppActorNotifierCubit cubit) =>
                      cubit.state.error.isEmpty)
                  ? () {
                      context.read<AppActorNotifierCubit>().openWhatsappChat();
                    }
                  : null,
              child: const Text("Open Whatsapp"),
            )
          ],
        ),
      ),
    );
  }
}
```

> Note:
> 
> * context.watch : Provides the closest ancestor instance of the specified type and listens to changes on the instance. Equal to BlocProvider.of(context, listen: true). [Learn more](https://bloclibrary.dev/#/flutterbloccoreconcepts?id=contextwatch).
>     
> * context.select : Same as context.watch, but allows you listen for changes in a smaller part of a state. [Learn more](https://bloclibrary.dev/#/flutterbloccoreconcepts?id=contextselect).
>     

## Conclusion

Now we can open a chat with any Whatsapp user without saving the contact in the device.

GitHub: [https://github.com/NirmalAriyathilake/blog\_whatsapp\_opener](https://github.com/NirmalAriyathilake/blog_whatsapp_opener)

> “Good code is its own best documentation. As you're about to add a comment, ask yourself, 'How can I improve the code so that this comment isn't needed?' Improve the code and then document it to make it even clearer.” - Steve McConnell
