분류 전체보기

1 2 3 4 ··· 8

- Flutter에서 MQTT를 이용한 발행(publish) & 구독(subscribe)

 

 

pubspec.yaml 추가

mqtt_client: ^9.6.2

mqtt_client 패키지 추가

 

 

코드 추가

  • MQTT 브로커 설정
Future<void> setupMqtt() async {
    // MQTT 브로커 연결
    client = MqttServerClient.withPort(broker, 'flutter_client', port);
    // MQTT 로그 출력
    client!.logging(on: false);

    // 리스너 등록
    client!.onConnected = onMqttConnected;
    client!.onDisconnected = onMqttDisconnected;
    client!.onSubscribed = onSubscribed;

    try {
      //
      await client!.connect();
    } catch (e) {
      print('Connected Failed.. \nException: $e');
    }
  }

 

 

  • MQTT publish
void publishData(String data) {
    final payload = jsonEncode(data);
    final builder = MqttClientPayloadBuilder();
    builder.addString(payload);

    client!.publishMessage(topic, MqttQos.exactlyOnce, builder.payload!);
  }

 

  • MQTT subscribe
// MQTT 연결 시 토픽 구독.
      client!.subscribe(topic, MqttQos.atLeastOnce);

      // 토픽 수신 리스너
      client!.updates!.listen((List<MqttReceivedMessage<MqttMessage>> c) {
        final MqttPublishMessage recMess = c[0].payload as MqttPublishMessage;
        final String message =
        MqttPublishPayload.bytesToStringAsString(recMess.payload.message);

        // 수신한 메시지 처리
        setState(() {
          print(':: Received message: $message');
        });
      });

 

 

  • 전체코드
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:mqtt_client/mqtt_client.dart';
import 'package:mqtt_client/mqtt_server_client.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.green,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final String broker = /*브로커 주소*/;     // MQTT broker address
  final int port = /*포트*/;                 // MQTT broker port
  final String topic = /*토픽명*/;           // MQTT topic

  MqttServerClient? client;
  bool connected = false;

  @override
  void initState() {
    super.initState();
    setupMqtt();  // MQTT Set
  }

  Future<void> setupMqtt() async {
    // MQTT 브로커 연결
    client = MqttServerClient.withPort(broker, 'flutter_client', port);
    // MQTT 로그 출력
    client!.logging(on: false);

    // 리스너 등록
    client!.onConnected = onMqttConnected;
    client!.onDisconnected = onMqttDisconnected;
    client!.onSubscribed = onSubscribed;

    try {
      //
      await client!.connect();
    } catch (e) {
      print('Connected Failed.. \nException: $e');
    }
  }

  void onMqttConnected() {
    print(':: MqttConnected');
    setState(() {
      connected = true;
      // MQTT 연결 시 토픽 구독.
      client!.subscribe(topic, MqttQos.atLeastOnce);

      // 토픽 수신 리스너
      client!.updates!.listen((List<MqttReceivedMessage<MqttMessage>> c) {
        final MqttPublishMessage recMess = c[0].payload as MqttPublishMessage;
        final String message =
        MqttPublishPayload.bytesToStringAsString(recMess.payload.message);

        // 수신한 메시지 처리
        setState(() {
          print(':: Received message: $message');
        });
      });
    });
  }

  void onMqttDisconnected() {
    print(':: MqttDisconnected');
    setState(() {
      connected = false;
    });
  }

  void onSubscribed(String topic) {
    print(':: Subscribed topic: $topic');
  }

  // 데이터 전송
  void publishData(String data) {
    final payload = jsonEncode(data);
    final builder = MqttClientPayloadBuilder();
    builder.addString(payload);

    client!.publishMessage(topic, MqttQos.exactlyOnce, builder.payload!);
    print(':: Send message: $data');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            connected
                ? Text('CONNECTED')
                : Text('DISCONNECTED'),
            SizedBox(height: 16,),
            ElevatedButton(
                onPressed: () async {
                  publishData('MQTT SEND DATA');
                },
                child: Text('BUTTON'),
            )
          ],
        ),
      ),
    );
  }
}

 

 


  • 앱 실행 시 'topic' 구독 확인

  • 버튼클릭 시 데이터 전송 (발행,구독을 모두 해둔 상태라 같이 보임)

  • MQTT Explorer로 데이터 확인

  • MQTT Explorer에서 데이터 publish 결과

 



'DatagramSocket'을 사용하여 UDP 브로드캐스트 메시지를 수신할 수 있습니다.

DatagramSocket 포트는 송신하는 쪽 포트를 적어주시면 됩니다. 

 

 

  •  AndroidManifest에서 인터넷 사용 권한을 추가.
<uses-permission android:name="android.permission.INTERNET" />

 

  • UdpBroadcastReceiver를 생성하여 메시지를 수신받는 쓰레드를 생성.
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

public class UdpBroadcastReceiver extends Thread {
    private boolean running;
    private DatagramSocket socket;
    private byte[] buf = new byte[256];

    public UdpBroadcastReceiver() {
        try {
            socket = new DatagramSocket(4445); // 4445 포트에서 수신
            socket.setBroadcast(true);
        } catch (SocketException e) {
            e.printStackTrace();
        }
    }

    public void run() {
        running = true;

        while (running) {
            try {
                DatagramPacket packet = new DatagramPacket(buf, buf.length);
                socket.receive(packet);

                InetAddress address = packet.getAddress();
                int port = packet.getPort();
                packet = new DatagramPacket(buf, buf.length, address, port);
                String received = new String(packet.getData(), 0, packet.getLength());

                // 수신된 메시지 처리
                Log.d("UdpBroadcastReceiver", "Received Message: " + received);

                if (received.equals("end")) {
                    running = false;
                    continue;
                }
            } catch (Exception e) {
                e.printStackTrace();
                running = false;
            }
        }
        socket.close();
    }
}

 

  • 메인에서 해당 쓰레드 실행.
UdpBroadcastReceiver receiver = new UdpBroadcastReceiver();
receiver.start();

 

 

 

 

 

 


수신받은 데이터를 메인에서 처리하려면 콜백 인터페이스를 사용하여 쓰레드가 완료했을 때 콜백을 통해 결과를 전달받을 수 있습니다.

 

  • 콜백 인터페이스 정의.
public interface ThreadCompleteListener {
    void onThreadComplete(final String result);
}

 

  • 쓰레드 수정.

쓰레드 작업 완료 시, 콜백 메서드를 호출할 수 있도록 설정.

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

public class UdpBroadcastReceiver extends Thread {
    private ThreadCompleteListener listener;
    private boolean running;
    private DatagramSocket socket;
    private byte[] buf = new byte[256];

    public UdpBroadcastReceiver(ThreadCompleteListener listener) {
        try {
            socket = new DatagramSocket(4445); // 4445 포트에서 수신
            socket.setBroadcast(true);
        } catch (SocketException e) {
            e.printStackTrace();
        }
    }

    public void run() {
        running = true;

        while (running) {
            try {
                DatagramPacket packet = new DatagramPacket(buf, buf.length);
                socket.receive(packet);

                InetAddress address = packet.getAddress();
                int port = packet.getPort();
                packet = new DatagramPacket(buf, buf.length, address, port);
                String received = new String(packet.getData(), 0, packet.getLength());

                // 수신된 메시지 처리
                Log.d("UdpBroadcastReceiver", "Received Message: " + received);

                if (received.equals("end")) {
                    running = false;
                    continue;
                }
            } catch (Exception e) {
                e.printStackTrace();
                running = false;
            }
        }
        socket.close();
    }
}

 

  • 메인에서 쓰레드 호출 수정.
UdpBroadcastReceiver receiver = new UdpBroadcastReceiver(this);
receiver.start();

 

  • 메인에서 onThreadComplete으로 쓰레드 결과 처리.

implements 추가

public class MainActivity extends AppCompatActivity implements ThreadCompleteListener {

 

onThreadComplete

@Override
    public void onThreadComplete(String result) {
        // 스레드 결과 처리.
        
        runOnUiThread(() -> {
        	// UI수정할 경우 runOnUiThread 사용.
        });
    }

 



IntentFilter를 생성시켜주고, 원하는 액션을 intentFilter에 등록시켜줍니다.

그런다음 BroadcastReceiver를 생성하여 onReceive 안에 다음과 같이 작성해주시면 됩니다.

화면꺼짐(ACTION_SCREEN_OFF)을 예로 했는데 화면켜짐(ACTION_SCREEN_ON)을 사용하시려면

intentFilter에 화면켜짐을 등록하시고 조건문을 통해서 사용해주시면 됩니다.

IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
BroadcastReceiver receiver = new BroadcastReceiver() {
	@Override
    public void onReceive(Context context, Intent intent) {
    	if(intent.getAction().equals(Intent.ACTION_SCREEN_OFF)){
    		// 원하는 코드작성
    	}
    }
};
registerReceiver(receiver, intentFilter);​

[Android] 인앱업데이트

2021. 5. 12. 10:20

1. implementation 추가

implementation 'com.google.android.play:core:1.10.0'

 

2. 인앱업데이트 메서드 추가

클래스 내부에 해당 메서드를 추가시켜주시고 onCreate()에서 호출해주시면

앱을 실행했을 때 상위버전이 존재하면 인앤업데이트 화면이 켜지게 됩니다.

아래 메서드에서 AppUpdateType.IMMEDIATE를 이용해서 앱을 강제로 업데이트가 가능합니다.

(강제 업데이트 외에 권장 업데이트 방법도 있습니다.)

인앱업데이트 확인방법은 베타테스트를 이용해서 확인하시면 됩니다.

// 인앱 강제 업데이트
private void updaterequest() {
    AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(getApplicationContext());
    Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();

    appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
        if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
                && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
            Log.d("WTF", "updaterequest:업데이트할 수 있음 ");
            try {
                appUpdateManager.startUpdateFlowForResult(
                        appUpdateInfo,
                        AppUpdateType.IMMEDIATE,
                        this,
                        UPDATE_REQUEST_CODE);
                Log.d("WTF", "updaterequest:업데이트 요청함 ");
            } catch (IntentSender.SendIntentException e) {
                e.printStackTrace();
            }
        }else{
            Log.d("WTF", "updaterequest:업데이트할 것이 없음 ");
        }
    });
}

 



앱 다시 실행하기


설정이 바뀌거나 기타 여러가지 경우에 앱을 다시 실행해야할 때가 있습니다.

public void restart(){
    Intent intent = getBaseContext().getPackageManager().
            getLaunchIntentForPackage(getBaseContext().getPackageName());
    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);
    getActivity().finishAffinity();
}

FLAG_ACTIVITY_CLEAR_TASK와 FLAG_ACTIVITY_NEW_TASK를 함께 사용하면

백그라운드에 남지 않고 다시 시작됩니다.

 

 



URL 유효성 체크(마켓에 유효한 앱인지 확인)


안드로이드 제작 중에 마켓에 등록되지 않은 앱을 intent를 통해 이동하게 되면

무한로딩이 걸리게 됩니다.

이런 예외처리를 어떻게 할지 생각해봤는데, URL 유효성 체크를 통해 유효한 페이지인지 확인하고

유효하지 않으면 조건문을 통해 걸러내도록 했습니다.

 

// 마켓에 앱 존재여부 확인
public static boolean checkAvailUrl(String url){
        boolean check = false;

        try{
            URL tempUrl  = new URL(url);
            HttpURLConnection connection = (HttpURLConnection)tempUrl.openConnection();
            connection.setRequestMethod("GET");
            connection.connect();

            if(200 == connection.getResponseCode()) check = true;
        }catch(IOException e){
            return false;
        }
        return check;
}

위 코드를 사용하면 마켓에 존재하지 않는 앱은 false를 리턴받아 처리할 수 있게 됩니다.

네트워크에 접속하게 되는데 바로 접속하게 되면 android.os.NetworkOnMainThreadException 에러가 발생합니다.

이를 해결하기 위해서는 Thread를 이용하시면 됩니다.

 

Thread workingThread = new Thread(() -> {
	isAppStoreAvail = checkAvailUrl(
	"https://play.google.com/store/apps/details?id=패키지명");
});
        
workingThread.start();

try {
	workingThread.join();
} catch (InterruptedException e) {
	e.printStackTrace();
}

if (isAppStoreAvail) {
	// 구글플레이에 해당 앱이 있을 경우
}

이런식으로 Thread에 넣어서 정상적으로 작동이 됩니다.

그냥 new Thread(() -> { }).start(); 를 사용해도 되지만 그럴 경우 Thread가 끝나기 전에 이미 다른 코드가 실행되기 때문에 Thread가 다 끝나고 다음코드가 진행될 수 있도록 join()을 사용해주시면 됩니다.



Handler mHandler = new Handler(Looper.getMainLooper());

 

mHandler.postDelayed((Runnable) () -> /*원하는 작업*/), /*시간*/);

Hanlder postDelayed를 이용해서 원하는 시간이 지난 후 실행이 되도록 설정이 가능합니다.

뒤에 딜레이 시간을 입력하면 됩니다. 1초는 1000으로 입력하시면 됩니다.

 

여러 개의 뷰에 동시에 사용할 경우에는 딜레이 시간이 다른 뷰에 간섭이 생길 수 있으므로

mHandler.removeMessages(0);

or

mHandler.removeCallbacksAndMessages(null);

이와 같이 초기화 해주시면 됩니다.



view.getResources().getResourceEntryName(view.getId())

getResourceEntryName을 이용해 위 코드를 사용하면

R.id.xxxx 에서 xxxx값만 가져올 수 있습니다.

 

 

 

 

 



런치패드에서 꾹 눌러도 제거 되지 않는 아이콘이 있습니다. 😳

이럴 경우에는

 

1. Finder를 열어서

2. Home -> 응용프로그램(Applications)를 들어가시면

삭제되지 않던 파일들이 있습니다.

 

해당 파일들을 제거해주시면 런치패드가 깔끔해집니다😊

제거 방법을 자꾸 까먹어서 기록도 할겸 올립니다.

 



EditText 천단위 콤마(,)설정하기

 


EditText를 작성할 때, TextWatcher를 이용해 천단위마다 콤마(,)를 찍을 수 있습니다.

여러 개의 EditText에 콤마(,) 설정을 하기위해서는 콤마설정을 하는 커스텀을 만들어주면 됩니다.

 

CustomTextWathcer 클래스

public class CustomTextWatcher implements TextWatcher {

    private EditText editText;
    String strAmount = "";

    CustomTextWatcher(EditText et) {
        editText = et;
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {

    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        if(!TextUtils.isEmpty(s.toString()) && !s.toString().equals(strAmount)) {
            strAmount = makeStringComma(s.toString().replace(",", ""));
            editText.setText(strAmount);
            Editable editable = editText.getText();
            Selection.setSelection(editable, strAmount.length());
        }
    }

    @Override
    public void afterTextChanged(Editable s) {

    }

    protected String makeStringComma(String str) {    // 천단위 콤마설정.
        if (str.length() == 0) {
            return "";
        }
        long value = Long.parseLong(str);
        DecimalFormat format = new DecimalFormat("###,###");
        return format.format(value);
    }
}

makeStringComma는 천단위마다 콤마설정을 합니다.

그리고 onTextChanged에서 EditText에 값이 입력될 때마다 천단위에 콤마를 찍어줍니다.

 

 

edittext.addTextChangedListener(new CustomTextWathcher(edittext));

addTextChangedListener로 커스텀(CustomTextWatcher)을 사용하면 됩니다.


+ Recent posts