어느 날 안드로이드 스튜디오에서 프로젝트를 불러왔는데 프로젝트 로딩되는 과정 중에 Scanning files to index 에서 넘어가지를 못 하는겁니다... 안드로이드 스튜디오를 껐다가 켜봐도 컴퓨터를 재부팅해봐도 똑같은 현상이 남아있더라구요. ...

어느 날 안드로이드 스튜디오에서 프로젝트를 불러왔는데

프로젝트 로딩되는 과정 중에 Scanning files to index 에서 넘어가지를 못 하는겁니다...

안드로이드 스튜디오를 껐다가 켜봐도 컴퓨터를 재부팅해봐도 똑같은 현상이 남아있더라구요.

찾아보니 아주 아주 간단하게 해결할 수 있는 방법을 찾을 수 있었습니다!








1. 해결 방법

[File] > [Invalidate Caches] 를 통해서 안드로이드 스튜디오를 재시작을 해줍니다.


저 같은 경우는 위와 같은 방법으로 바로 해결이 되었는데요,

그래도 계속해서 똑같은 현상이 나온다면 아래 방법을 해보시길 바랍니다.


 - 해당 프로젝트 경로에 .idea 를 삭제하고 안드로이드 스튜디오 재실행

 - 안드로이드 스튜디오 재설치






[Reference]

[Android Studio, Issue] Scanning files to index, Indexing stuck




날짜를 선택에 활용되는 DatePickerDialog 예제를 기록하려고 합니다. 해당 예제는 위 그림과 같이 시작 날짜 ~ 끝 날짜를 설정하는 내용을 담고 있습니다.             1. DatePickerDialog (kotlin) class ...

날짜를 선택에 활용되는 DatePickerDialog 예제를 기록하려고 합니다.

해당 예제는 위 그림과 같이 시작 날짜 ~ 끝 날짜를 설정하는 내용을 담고 있습니다.


 

 

 

 

 

 

1. DatePickerDialog (kotlin)

class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding

private var startCalendar = Calendar.getInstance()
private var endCalendar = Calendar.getInstance()
private val dateFormat = SimpleDateFormat("yyyy-MM-dd")

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

startCalendar.add(Calendar.MONTH, -1) // startCalendar 한달 전으로 설정

binding.btnStartDate.text = dateFormat.format(startCalendar.time)
binding.btnEndDate.text = dateFormat.format(endCalendar.time)

binding.btnStartDate.setOnClickListener {
// 날짜 선택 리스너 정의
val onDateSetListener = DatePickerDialog.OnDateSetListener { _, year, month, day ->
startCalendar.set(Calendar.YEAR, year)
startCalendar.set(Calendar.MONTH, month)
startCalendar.set(Calendar.DAY_OF_MONTH, day)

binding.btnStartDate.text = dateFormat.format(startCalendar.time)
}

// DatePickerDialog 생성, 날짜 선택 리스너 등록 및 날짜 설정
val datePickerDialog = DatePickerDialog(
this,
onDateSetListener,
startCalendar.get(Calendar.YEAR),
startCalendar.get(Calendar.MONTH),
startCalendar.get(Calendar.DAY_OF_MONTH)
)

// DatePickerDialog Show
datePickerDialog.show()
}

binding.btnEndDate.setOnClickListener {
// 날짜 선택 리스너 정의
val onDateSetListener = DatePickerDialog.OnDateSetListener { _, year, month, day ->
endCalendar.set(Calendar.YEAR, year)
endCalendar.set(Calendar.MONTH, month)
endCalendar.set(Calendar.DAY_OF_MONTH, day)

binding.btnEndDate.text = dateFormat.format(endCalendar.time)
}

// DatePickerDialog 생성, 날짜 선택 리스너 등록 및 날짜 설정
val datePickerDialog = DatePickerDialog(
this,
onDateSetListener,
endCalendar.get(Calendar.YEAR),
endCalendar.get(Calendar.MONTH),
endCalendar.get(Calendar.DAY_OF_MONTH)
)

// DatePickerDialog Show
datePickerDialog.show()
}
}
}

 

 

 

 

 

 

 

2. DatePickerDialog (java)

public class MainActivity extends AppCompatActivity {

private Calendar startCalendar = Calendar.getInstance();
private Calendar endCalendar = Calendar.getInstance();
private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Button btnStartDate = findViewById(R.id.btnStartDate);
Button btnEndDate = findViewById(R.id.btnStartDate);

startCalendar.add(Calendar.MONTH, -1); // startCalendar 한달 전으로 설정

btnStartDate.setText(dateFormat.format(startCalendar.getTime()));
btnEndDate.setText(dateFormat.format(endCalendar.getTime()));

btnStartDate.setOnClickListener(view -> {
// 날짜 선택 리스너 정의
DatePickerDialog.OnDateSetListener onDateSetListener = (v, year, month, day) -> {
startCalendar.set(Calendar.YEAR, year);
startCalendar.set(Calendar.MONTH, month);
startCalendar.set(Calendar.DAY_OF_MONTH, day);

btnStartDate.setText(dateFormat.format(startCalendar.getTime()));
};

// DatePickerDialog 생성, 날짜 선택 리스너 등록 및 날짜 설정
DatePickerDialog datePickerDialog = new DatePickerDialog(
this,
onDateSetListener,
startCalendar.get(Calendar.YEAR),
startCalendar.get(Calendar.MONTH),
startCalendar.get(Calendar.DAY_OF_MONTH)
);

// DatePickerDialog Show
datePickerDialog.show();
});

btnEndDate.setOnClickListener(view -> {
// 날짜 선택 리스너 정의
DatePickerDialog.OnDateSetListener onDateSetListener = (v, year, month, day) -> {
endCalendar.set(Calendar.YEAR, year);
endCalendar.set(Calendar.MONTH, month);
endCalendar.set(Calendar.DAY_OF_MONTH, day);

btnEndDate.setText(dateFormat.format(endCalendar.getTime()));
};

// DatePickerDialog 생성, 날짜 선택 리스너 등록 및 날짜 설정
DatePickerDialog datePickerDialog = new DatePickerDialog(
this,
onDateSetListener,
endCalendar.get(Calendar.YEAR),
endCalendar.get(Calendar.MONTH),
endCalendar.get(Calendar.DAY_OF_MONTH)
);

// DatePickerDialog Show
datePickerDialog.show();
});
}
}

 

 

 

 

3. layout 구성

<layout xmlns:android="http://schemas.android.com/apk/res/android">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="horizontal">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:gravity="center"
android:orientation="horizontal">

<Button
android:id="@+id/btnStartDate"
android:layout_width="160dp"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="17dp" />

<TextView
android:layout_width="20dp"
android:layout_height="wrap_content"
android:gravity="center"
android:text="~"
android:textSize="17dp" />

<Button
android:id="@+id/btnEndDate"
android:layout_width="160dp"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="17dp" />

</LinearLayout>
</LinearLayout>
</layout>

 

 

 

 

Flutter는 하나의 코드 베이스를 사용하여 여러 플랫폼(Android, IOS, Desktop, Web) 애플리케이션을 구축할 수 있는 강점을 가지고 있습니다. Flutter를 활용해서 Mobile to Mobile, PC to Mobile 서로 ...

Flutter는 하나의 코드 베이스를 사용하여 여러 플랫폼(Android, IOS, Desktop, Web)

애플리케이션을 구축할 수 있는 강점을 가지고 있습니다.

Flutter를 활용해서 Mobile to Mobile, PC to Mobile 서로 간단하게 메세지를 주고받는

WiFi 소켓 통신 예제를 작성하고 확인한 내용을 정리합니다.

 







1. dart:io 라이브러리

소켓 통신을 하기 위해 Flutter에서 지원 해주는 dart:io 라이브러리를 사용합니다.

 

import 'dart:io';

 

dart:io를 사용하면 파일, 디렉터리, 서버 및 클라이언트, 소켓, HTTP 등을 작업할 수 있습니다.

(단, 브라우저 기반 앱은 dart:io 사용을 못 한다고 하네요)








2. 서버(Server)

"flutter_socket_server" 이름으로 새로운 프로젝트를 만들었습니다.

주요 소스는 아래와 같습니다.

import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';

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

@override
State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
final _msgController = TextEditingController();
ServerSocket? serverSocket;
Socket? clientSocket;
int port = 5555;
StringBuffer logcat = StringBuffer();

void startServer() async {
setState(() {
logcat.write("Start Server : waiting client...\n");
});

serverSocket = await ServerSocket.bind(InternetAddress.anyIPv4, port, shared: true);
serverSocket?.listen((socket) {
setState(() {
clientSocket = socket;
logcat.write(
"Socket Connected - ${socket.remoteAddress.address}:${socket.remotePort}\n");
});

socket.listen(
(onData) {
setState(() {
logcat.write("Receive : ${utf8.decode(onData)}\n");
});
},
onDone: () {
disconnect();
},
onError: (e) {
logcat.write("exception : $e\n");
disconnect();
},
);
});
}

void stopServer() {
serverSocket?.close();

setState(() {
serverSocket = null;
logcat.write("Stop Server\n");
});
}

void disconnect() {
clientSocket?.close();

setState(() {
clientSocket = null;
logcat.write("Socket Disconnected\n");
});
}

void sendMessage() {
if (_msgController.text.isEmpty) return;

clientSocket?.write(_msgController.text);

setState(() {
logcat.write("Send : ${_msgController.text}\n");
_msgController.clear();
});
}

@override
void initState() {
super.initState();

startServer();
}

@override
void dispose() {
super.dispose();

stopServer();

_msgController.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter Socket Server(서버)')),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
controllerView(),
logcatView(),
sendMessageView(),
],
),
),
);
}

Widget controllerView() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 15),
Text(
clientSocket == null ? 'DisConnected' : 'Connected',
style: TextStyle(
color: clientSocket == null ? Colors.red : Colors.green,
),
),
const SizedBox(height: 15),
ElevatedButton(
onPressed: () {
if (serverSocket == null) {
startServer();
} else {
stopServer();
}
},
style: ElevatedButton.styleFrom(
minimumSize: const Size.fromHeight(50),
),
child: Text(serverSocket == null ? '서버 시작' : '서버 중지'),
),
],
);
}

Widget logcatView() {
return Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: SingleChildScrollView(
child: SizedBox(
width: double.infinity,
child: Text(
logcat.toString(),
),
),
),
),
);
}

Widget sendMessageView() {
return Card(
child: ListTile(
title: TextField(
controller: _msgController,
),
trailing: IconButton(
icon: const Icon(Icons.send),
color: Colors.blue,
disabledColor: Colors.grey,
onPressed: (clientSocket != null) ? sendMessage : null,
),
),
);
}
}


모바일 실행 모습







3. 클라이언트(Client)

"flutter_socket_client" 이름으로 새로운 프로젝트를 만들었습니다.

주요 소스는 아래와 같습니다.

import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';

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

@override
State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
final _msgController = TextEditingController();
final _ipAddressController = TextEditingController();
Socket? clientSocket;
int port = 5555;
StringBuffer logcat = StringBuffer();

void connectToServer(String ipAddress) async {
Socket.connect(ipAddress, port, timeout: const Duration(seconds: 5))
.then((socket) {

setState(() {
clientSocket = socket;
logcat.write("Socket Connected - ${socket.remoteAddress.address}:${socket.remotePort}\n");
});

socket.listen(
(onData) {
setState(() {
logcat.write("Receive : ${utf8.decode(onData)}\n");
});
},
onDone: () {
disconnect();
},
onError: (e) {
logcat.write("exception : $e\n");
disconnect();
},
);
}).catchError((e) {
logcat.write("exception : $e\n");
});
}

void disconnect() {
clientSocket?.close();

setState(() {
clientSocket = null;
logcat.write("Socket Disconnected\n");
});
}

void sendMessage() {
if (_msgController.text.isEmpty) return;

clientSocket?.write(_msgController.text);

setState(() {
logcat.write("Send : ${_msgController.text}\n");
_msgController.clear();
});
}

@override
void initState() {
super.initState();
}

@override
void dispose() {
super.dispose();

disconnect();

_ipAddressController.dispose();
_msgController.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter Socket Client(클라이언트)')),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
controllerView(),
logcatView(),
sendMessageView(),
],
),
),
);
}

Widget controllerView() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 15),
Text(
clientSocket == null ? 'DisConnected' : 'Connected',
style: TextStyle(
color: clientSocket == null ? Colors.red : Colors.green),
),
const SizedBox(height: 15),
TextFormField(
controller: _ipAddressController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: 'IP 주소',
),
keyboardType: TextInputType.number,
validator: (value) {
if (value == null || value.isEmpty) {
return 'IP 주소를 입력하세요.';
}
return null;
},
),
const SizedBox(height: 15),
ElevatedButton(
onPressed: () {
if (clientSocket == null) {
connectToServer(_ipAddressController.text);
} else {
disconnect();
}
},
style: ElevatedButton.styleFrom(
minimumSize: const Size.fromHeight(50),
),
child: Text(clientSocket == null ? '연결' : '연결 해제'),
),
],
);
}

Widget logcatView() {
return Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: SingleChildScrollView(
child: SizedBox(
width: double.infinity,
child: Text(
logcat.toString(),
),
),
),
),
);
}

Widget sendMessageView() {
return Card(
child: ListTile(
title: TextField(
controller: _msgController,
),
trailing: IconButton(
icon: const Icon(Icons.send),
color: Colors.blue,
disabledColor: Colors.grey,
onPressed: (clientSocket != null) ? sendMessage : null,
),
),
);
}
}


테블릿 실행 모습







4. 소스 다운로드

Github:flutter_socket_server

Github:flutter_socket_client








5. Mobile to Mobile 소켓 통신 확인하기

소켓 통신을 하기위해서는 먼저 서로 다른 기기 간에 네트워크를 연결해줘야 합니다.

하나의 단말에 모바일 핫스팟(AP)을 켜주고 나머지 단말에서 WiFi를 연결 시켜 줍니다.

기기간에 WiFi가 연결되었다면 IP가 할당되었을 것이고,

[설정] > [모바일 핫스팟] > [연결된 기기]를 통해 할당된 IP 주소를 확인할 수 있습니다.


 

연결된 IP 주소를 확인하고 아래와 같이 연결 및 간단한 소켓 통신을 확인 할 수 있었습니다.


모바일 Server(WiFi)



테블릿 Client(WiFi AP)







6. PC to Mobile 소켓 통신 확인하기

PC to Mobile도 마찬가지로 기기간에 WiFi를 통해 네트워크를 연결해 줍니다.

저 같은 경우에는 PC에서 핫스팟(AP)을 켜주고 모바일에서 WiFi로 연결해 주었습니다.

이렇게 기기간에 연결이 완료되었다면,

PC에서 [설정] > [네트워크 및 인터넷] > [모바일 핫스팟] 연결된 IP주소를 확인할 수 있습니다.



연결된 IP 주소를 확인하고 아래와 같이 연결 및 간단한 소켓 통신을 확인 할 수 있었습니다.


모바일(WiFi)

 

 

PC(WiFi AP)






[Reference]

Dart Dev - dart:io

Flutter Example - TCP Socket Server

Flutter Example - TCP Socket Client