플러터 인앱 디버그 콘솔 (In app Debug Console)
Unity에는 In app debug console이라는 편리한 에셋이 있습니다.
플러터에서도 이런 위젯이 있으면 기기에서 쉽게 디버깅 로그를 볼 수 있을 것 같았습니다.
그래서 만들어보았습니다.
[영상]
구현방법이 궁금하신 분들은 계속 읽어주세요.
저는 보통의 debugPrint 대신에 아래와 같이 printDebug를 만들어서 사용하기 때문에,
저의 경우에는 debugPrint를 재정의 해줄 필요가 없습니다.
1
2
3
4
5
6
7
8
9
10
void printDebug(Object message) {
if (kDebugMode) {
// Use your LogManager to store logs
final logManager = LogManager(); // Ideally, you get this instance from your provider or some other state management solution.
logManager.addLog(message.toString());
// Print to console as well
print(message);
}
}
만약 debugPrint를 쓰시는 분은 아래와 같이 main에서 재정의해주세요.
1
2
3
4
5
6
7
8
9
10
11
12
void main() async {
WidgetsFlutterBinding.ensureInitialized();
debugPrint = (String? message, {int? wrapWidth}) {
// Add your log to the LogManager instance
final logManager = LogManager();
logManager.addLog(message ?? '');
// // You could also print it to the console
// print(message);
};
}
저는 아래와 같이 직접 만든 DraggableFloatingActionButton을 사용했습니다. 만약 똑같이 사용하고 싶으시다면, 제가 이전 포스팅에서 소개한 제 코드를 참조해주세요. https://blog.naver.com/devramyun/223328491112
[20240120] Flutter Draggable floating button Button에 대한 full code. 화면 Widget에서 사용할 때는 Stack에 넣어서 사용. Button에 대한 full code. 화면 Widget에서 사용할 때는 Stack에 넣어서 사용.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class MyHomePage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
return PopScope(
child: Scaffold(
...
body: Stack(
children: [
DraggableFloatingActionButton(
onPressed: () {
// Your onPressed code here
_openDebugLogMonitor(context, ref);
},
buttonIcon: const Icon(Icons.bug_report),
tooltipMessage: 'Open DebugConsole',
), // DraggableFloatingButton
], // []
), // Stack
), // Scaffold
), // PopScope
}
그리고 저는 riverpod를 사용해서 상태관리를 해주고 있기 때문에 아래와 같이 Provider를 정의해줍니다.
1
2
3
4
// Define a provider for LogManager
final logManagerProvider = Provider((ref) {
return LogManager();
});
이제 본격적인 인앱 디버그 로그 모니터 클래스입니다.
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Assume you have a log manager somewhere in your app
class LogManager {
// Singleton pattern for easy access anywhere in your app
static final LogManager _instance = LogManager._internal();
factory LogManager() => _instance;
LogManager._internal();
// This will store all the logs
final List _logs = [];
// Function to add logs
void addLog(String log) {
_logs.add(log);
// If you have a listener mechanism, notify listeners here
}
// Getter to retrieve logs
List get logs => _logs;
// Function to filter logs based on a search query
List filterLogs(String query) {
if (query.isEmpty) {
// If the query is empty, return all logs
return _logs;
} else {
// Filter logs based on the search query
return _logs.where((log) => log.contains(query)).toList();
}
}
}
Future _openDebugLogMonitor(BuildContext context, WidgetRef ref) async {
final logManager = ref.read(logManagerProvider);
// Create a TextEditingController for the search input
final TextEditingController searchController = TextEditingController();
// Create a ScrollController to scroll to the bottom of the list
final ScrollController scrollController = ScrollController();
// Create a variable to store the filtered logs
List filteredLogs = logManager.logs; // Initialize with all logs
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
void clearSearch() {
setState(() {
searchController.clear();
filteredLogs = logManager.logs;
});
}
void scrollToTop() {
scrollController.animateTo(
scrollController.position.maxScrollExtent,
duration: Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
}
void scrollToBottom() {
scrollController.animateTo(
0.0,
duration: Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
}
return Container(
height: 500,
color: Colors.white,
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: TextField(
controller: searchController,
decoration: InputDecoration(
labelText: 'Search Log',
prefixIcon: Icon(Icons.search),
),
onChanged: (query) {
setState(() {
filteredLogs = logManager.logs
.where((log) =>
query.isEmpty || log.contains(query))
.toList();
});
},
),
),
IconButton(
icon: Icon(Icons.clear),
onPressed: clearSearch,
),
IconButton(
icon: Icon(Icons.arrow_upward),
onPressed: scrollToTop,
),
IconButton(
icon: Icon(Icons.arrow_downward),
onPressed: scrollToBottom,
),
],
),
),
Expanded(
child: Scrollbar(
thickness: 8,
thumbVisibility: true, // Show scrollbar always
controller: scrollController,
// Wrap the ListView with a Scrollbar
child: ListView.builder(
// Color the last item in the list as grey
itemExtent: 70,
controller: scrollController,
reverse: true, // Set reverse to true to reverse the order of items
itemCount: filteredLogs.length,
itemBuilder: (BuildContext context, int index) {
final reversedIndex = filteredLogs.length - 1 - index;
final isLastItem = reversedIndex == filteredLogs.length - 1;
return Container(
color: isLastItem ? Colors.lightBlue : null, // Set background color conditionally
child: ListTile(
title: Text(
filteredLogs[reversedIndex],
style: TextStyle(
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
color: isLastItem ? Colors.white : Colors.black,
),
),
),
);
},
),
),
),
],
),
);
},
);
},
);
}
만약 제 버전에서 더 업그레이드를 하시게 된다면 저에게도 알려주세요. 감사합니다.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

