Flutter plugin 개발하기 - part3 Android 환경 세팅 (Kotlin) 및 개발하기

Flutter plugin 개발하기 - part3 Android 환경 세팅 (Kotlin) 및 개발하기

이야기를 하기에 앞서, 이 글은 네가지 파트로 나뉘어져 있습니다. 각 글을 나눈 기준은 제가 항상 Flutter로 개발하면서 작업을 나누는 단위입니다. 각 단위 내에서도 더 작게 쪼개지지만 플랫폼 관련 기능을 구현할 때는 Flutter -> iOS -> Android 순서로 작업합니다. (Android보다 iOS쪽이 제약이 많기 때문에 기능 확인차 이렇게 하고있습니다.)

프로젝트 열기

필자는 Flutter plugin을 개발할 때 난관이 “어느 프로젝트를 열어야 하는거지?” 였다. iOS쪽은 애를 먹었지만, Android쪽은 안드로이드 스튜디오에서 여니 찾기 편했다.

iOS와 동일하게 example 내부의 Android 프로젝트를 열어서 개발해야한다!

백문이 불여일견! Android studio로 example Android 프로젝트를 열었을 때의 directory tree를 살펴보자. Android studio에서 wifi_connector/example/android 를 열면 된다.

  • Android Studio에서 디렉토리 뷰를 project뷰로 설정하면 3가지 프로젝트 (app, wifi_connector, android)가 잡혀있는 것을 볼 수 있다.
    • 1번은 wifi_connector 내부에 있는 파일들이 우리가 개발중인 플러그인의 android쪽이이다.
      • wifi 연결하는 기능을 저 내부에 WifiConnectorPlugin에 개발하고, method channel을 call해서 사용하면 된다.
    • 2번은 android 내부에 있는 파일들은 example 프로젝트의 파일들이다. 우리는 여기서 건드릴것이 없다. 플러그인에서 구현된 기능을 flutter쪽에서 가져다 쓸 것이기 때문이다

코드 설명

Plugin이 Activity와 연결될 때 context를 받아와서 저장 해 두고 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.wonjerry.wifi_connector

class WifiConnectorPlugin : MethodCallHandler, FlutterPlugin {
companion object {
@JvmStatic
fun registerWith(registrar: Registrar) {
val instance = WifiConnectorPlugin()
// activity의 context를 받아오기위해 FlutterPlugin을 상속받고, interface들을 구현한다.
instance.onAttachedToEngine(registrar.context(), registrar.messenger())
}
}

private var activityContext: Context? = null
private var methodChannel: MethodChannel? = null

// Plugin이 Flutter Activity와 연결되었을 때 불리는 callback method이다.
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
onAttachedToEngine(binding.applicationContext, binding.flutterEngine.dartExecutor)
}

// Plugin이 Flutter Activity와 연결이 떨어졌을 때 불리는 callback method이다.
override fun onDetachedFromEngine(p0: FlutterPlugin.FlutterPluginBinding) {
activityContext = null
methodChannel?.setMethodCallHandler(null)
methodChannel = null
}

// Plugin이 Flutter Activity와 연결되었을 때 context를 저장 해 둔다.
private fun onAttachedToEngine(context: Context, messenger: BinaryMessenger) {
activityContext = context
methodChannel = MethodChannel(messenger, "wifi_connector").apply {
setMethodCallHandler(this@WifiConnectorPlugin)
}
}

method channel을 통해 온 call을 handling 한다.

1
2
3
4
5
6
override fun onMethodCall(call: MethodCall, result: Result) {
when (call.method) {
"connectToWifi" -> connectToWifi(call, result)
else -> result.notImplemented()
}
}

ssid, password를 받아서 wifi에 연결한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
private fun connectToWifi(call: MethodCall, result: MethodChannel.Result) {
val argMap = call.arguments as Map
val ssid = argMap["ssid"] as String
val password = argMap["password"] as String?

// 비밀번호가 있냐 없냐에 따라 wifi configration을 설정한다.
val wifiConfiguration =
if (password == null) {
WifiConfiguration().apply {
SSID = wrapWithDoubleQuotes(ssid)
status = WifiConfiguration.Status.CURRENT
allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE)
}
} else {
WifiConfiguration().apply {
SSID = wrapWithDoubleQuotes(ssid)
preSharedKey = wrapWithDoubleQuotes(password)
status = WifiConfiguration.Status.CURRENT
allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK)
}
}

val wifiManager = activityContext?.applicationContext?.getSystemService(Context.WIFI_SERVICE) as WifiManager

with(wifiManager) {
if (!isWifiEnabled) {
isWifiEnabled = true
}

// 위에서 생성한 configration을 추가하고 해당 네트워크와 연결한다.
addNetwork(wifiConfiguration)
configuredNetworks.find { network ->
network.SSID == wrapWithDoubleQuotes(ssid)
}?.let { network ->
disconnect()
enableNetwork(network.networkId, true)
reconnect()
result.success(true)
} ?: let {
result.success(false)
}
}
}

wifi 권한

Android에서 wifiManager를 통해 programming 적으로 wifi를 제어하기 위해서는 권한이 필요하다.

AndroidManifest.xml에 권한 두가지를 추가 해 준다.

1
2
3
4
5
6

package="com.wonjerry.wifi_connector">





Total code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
package com.wonjerry.wifi_connector

import android.content.Context
import android.net.wifi.WifiConfiguration
import android.net.wifi.WifiManager
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.Registrar

class WifiConnectorPlugin : MethodCallHandler, FlutterPlugin {
companion object {
@JvmStatic
fun registerWith(registrar: Registrar) {
val instance = WifiConnectorPlugin()
// activity의 context를 받아오기위해 FlutterPlugin을 상속받고, interfacee들을 구현한다.
instance.onAttachedToEngine(registrar.context(), registrar.messenger())
}
}

private var activityContext: Context? = null
private var methodChannel: MethodChannel? = null

// Plugin이 Flutter Activity와 연결되었을 때 불리는 callback method이다.
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
onAttachedToEngine(binding.applicationContext, binding.flutterEngine.dartExecutor)
}

// Plugin이 Flutter Activity와 연결이 떨어졌을 때 불리는 callback method이다.
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
activityContext = null
methodChannel?.setMethodCallHandler(null)
methodChannel = null
}

// Plugin이 Flutter Activity와 연결되었을 때 context를 저장 해 둔다.
private fun onAttachedToEngine(context: Context, messenger: BinaryMessenger) {
activityContext = context
methodChannel = MethodChannel(messenger, "wifi_connector").apply {
setMethodCallHandler(this@WifiConnectorPlugin)
}
}


override fun onMethodCall(call: MethodCall, result: Result) {
when (call.method) {
"connectToWifi" -> connectToWifi(call, result)
else -> result.notImplemented()
}
}

private fun connectToWifi(call: MethodCall, result: Result) {
val argMap = call.arguments as Map
val ssid = argMap["ssid"] as String
val password = argMap["password"] as String?

// 비밀번호가 있냐 없냐에 따라 wifi configration을 설정한다.
val wifiConfiguration =
if (password == null) {
WifiConfiguration().apply {
SSID = wrapWithDoubleQuotes(ssid)
status = WifiConfiguration.Status.CURRENT
allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE)
}
} else {
WifiConfiguration().apply {
SSID = wrapWithDoubleQuotes(ssid)
preSharedKey = wrapWithDoubleQuotes(password)
status = WifiConfiguration.Status.CURRENT
allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK)
}
}

val wifiManager = activityContext?.applicationContext?.getSystemService(Context.WIFI_SERVICE) as WifiManager

with(wifiManager) {
if (!isWifiEnabled) {
isWifiEnabled = true
}

// 위에서 생성한 configration을 추가하고 해당 네트워크와 연결한다.
addNetwork(wifiConfiguration)
configuredNetworks.find { network ->
network.SSID == wrapWithDoubleQuotes(ssid)
}?.let { network ->
disconnect()
enableNetwork(network.networkId, true)
reconnect()
result.success(true)
} ?: let {
result.success(false)
}
}

}

private fun wrapWithDoubleQuotes(text: String) = "\"$text\""
}

실제 동작 확인

자 이제 example앱을 빌드하고, ssid, password를 입력하여 wifi에 연결 해 보자!

성공적으로 wifi에 연결되었다! 다음 글에서는 지금까지 구현한 wifi connector를 pub.dev에 배포해보자!

댓글

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×