1
- /// Provides utilities for handiling single instancing in Flutter .
1
+ /// A simple way to check if your application is already running .
2
2
///
3
- /// A simple usage example:
3
+ /// ---
4
4
///
5
5
/// ```dart
6
+ /// import 'dart:io';
7
+ ///
8
+ /// import 'package:flutter/material.dart';
6
9
/// import 'package:flutter_single_instance/flutter_single_instance.dart';
7
10
///
8
- /// main() async {
11
+ /// void main() async {
9
12
/// WidgetsFlutterBinding.ensureInitialized();
13
+ /// await windowManager.ensureInitialized();
10
14
///
11
- /// if(await FlutterSingleInstance().isFirstInstance()){
12
- /// runApp(MyApp());
13
- /// }else{
15
+ /// if (await FlutterSingleInstance().isFirstInstance()) {
16
+ /// runApp(const MyApp());
17
+ /// } else {
14
18
/// print("App is already running");
15
19
///
20
+ /// final err = await FlutterSingleInstance().focus();
21
+ ///
22
+ /// if (err != null) {
23
+ /// print("Error focusing running instance: $err");
24
+ /// }
25
+ ///
16
26
/// exit(0);
17
27
/// }
18
28
/// }
19
29
/// ```
20
30
library flutter_single_instance;
21
31
32
+ export 'package:window_manager/window_manager.dart' show windowManager;
33
+
34
+ import 'dart:convert' ;
22
35
import 'dart:io' ;
23
36
import 'package:flutter/foundation.dart' ;
37
+ import 'package:flutter_single_instance/src/focus.dart' ;
38
+ import 'package:flutter_single_instance/src/generated/focus.pbgrpc.dart' ;
39
+ import 'package:flutter_single_instance/src/instance.dart' ;
40
+ import 'package:grpc/grpc.dart' ;
24
41
import 'package:path_provider/path_provider.dart' ;
25
42
import 'src/linux.dart' ;
26
43
import 'src/macos.dart' ;
@@ -30,24 +47,27 @@ import 'src/unsupported.dart';
30
47
/// Provides utilities for checking if this is the first instance of the app.
31
48
/// Make sure to call `WidgetsFlutterBinding.ensureInitialized()` before using this class.
32
49
abstract class FlutterSingleInstance {
33
- static FlutterSingleInstance ? _instance ;
50
+ static FlutterSingleInstance ? _singelton ;
34
51
52
+ /// Internal constructor for implementations.
35
53
@protected
36
- const FlutterSingleInstance .internal ();
54
+ FlutterSingleInstance .internal ();
55
+
56
+ Server ? _server;
57
+ Instance ? _instance;
37
58
59
+ /// Provides utilities for checking if this is the first instance of the app.
60
+ /// Make sure to call `WidgetsFlutterBinding.ensureInitialized()` before using this class.
38
61
factory FlutterSingleInstance () {
39
- _instance ?? = kIsWeb || Platform .isAndroid || Platform .isIOS
40
- ? const FlutterSingleInstanceUnsopported ()
41
- : Platform .isMacOS
42
- ? const FlutterSingleInstanceMacOS ()
43
- : Platform .isLinux
44
- ? const FlutterSingleInstanceLinux ()
45
- : Platform .isWindows
46
- ? const FlutterSingleInstanceWindows ()
47
- : throw UnsupportedError (
48
- 'Platform ${Platform .operatingSystem } is not supported.' );
49
-
50
- return _instance! ;
62
+ _singelton ?? = Platform .isMacOS
63
+ ? FlutterSingleInstanceMacOS ()
64
+ : Platform .isLinux
65
+ ? FlutterSingleInstanceLinux ()
66
+ : Platform .isWindows
67
+ ? FlutterSingleInstanceWindows ()
68
+ : FlutterSingleInstanceUnsopported ();
69
+
70
+ return _singelton! ;
51
71
}
52
72
53
73
/// If enabled [FlutterSingleInstance.isFirstInstance] will always return true.
@@ -71,36 +91,42 @@ abstract class FlutterSingleInstance {
71
91
var pidFile = await getPidFile (processName);
72
92
pidFile! ;
73
93
74
- if (pidFile.existsSync ()) {
75
- var pid = int .parse (pidFile.readAsStringSync ());
94
+ if (! pidFile.existsSync ()) {
95
+ // No pid file, so this is the first instance.
96
+ await activateInstance (processName);
97
+ return true ;
98
+ }
76
99
77
- var pidName = await getProcessName (pid );
100
+ final data = await pidFile. readAsString ( );
78
101
79
- if (processName != pidName) {
80
- // Process does not exist, so we can activate this instance.
81
- await _activateInstance (processName);
102
+ _instance = Instance .fromJson (jsonDecode (data));
82
103
83
- return true ;
84
- } else {
85
- // Process exists, so this is not the first instance.
86
- return false ;
87
- }
88
- } else {
89
- // No pid file, so this is the first instance.
90
- await _activateInstance (processName);
104
+ final pidName = await getProcessName (_instance! .pid);
91
105
92
- return true ;
106
+ if (processName == pidName) {
107
+ // Process exists, so this is not the first instance.
108
+ return false ;
93
109
}
110
+
111
+ // Process does not exist, so we can activate this instance.
112
+ await activateInstance (processName);
113
+
114
+ return true ;
94
115
}
95
116
96
- /// Activates the first instance of the app.
97
- /// Writes a pid file to the temp directory.
98
- Future <void > _activateInstance (String processName) async {
117
+ /// Activates the first instance of the app and writes a pid file to the temp directory .
118
+ @protected
119
+ Future <void > activateInstance (String processName) async {
99
120
var pidFile = await getPidFile (processName);
100
121
101
122
if (pidFile? .existsSync () == false ) await pidFile? .create ();
102
123
103
- await pidFile? .writeAsString (pid.toString ());
124
+ final instance = Instance (
125
+ pid: pid,
126
+ port: await startRpcServer (),
127
+ );
128
+
129
+ await pidFile? .writeAsString (jsonEncode (instance.toJson ()));
104
130
}
105
131
106
132
/// Returns the pid file.
@@ -110,4 +136,53 @@ abstract class FlutterSingleInstance {
110
136
111
137
return File ('${tmp .path }/$processName .pid' );
112
138
}
139
+
140
+ /// Starts an RPC server that listens for focus requests.
141
+ @protected
142
+ Future <int > startRpcServer () async {
143
+ _server = Server .create (
144
+ services: [FocusService ()],
145
+ codecRegistry: CodecRegistry (
146
+ codecs: const [
147
+ GzipCodec (),
148
+ IdentityCodec (),
149
+ ],
150
+ ),
151
+ );
152
+
153
+ await _server! .serve (port: 0 );
154
+
155
+ return _server! .port! ;
156
+ }
157
+
158
+ /// Focuses the running instance of the app and
159
+ /// returns `null` if the operation was successful or an error message if it failed.
160
+ Future <String ?> focus () async {
161
+ if (_instance == null ) return "No instance to focus" ;
162
+ if (_server != null ) return "This is the first instance" ;
163
+
164
+ try {
165
+ final channel = ClientChannel (
166
+ 'localhost' ,
167
+ port: _instance! .port,
168
+ options: ChannelOptions (
169
+ credentials: ChannelCredentials .insecure (),
170
+ codecRegistry: CodecRegistry (
171
+ codecs: const [
172
+ GzipCodec (),
173
+ IdentityCodec (),
174
+ ],
175
+ ),
176
+ ),
177
+ );
178
+
179
+ final client = FocusServiceClient (channel);
180
+
181
+ final response = await client.focus (FocusRequest ());
182
+
183
+ return response.success ? null : response.error;
184
+ } catch (e) {
185
+ return e.toString ();
186
+ }
187
+ }
113
188
}
0 commit comments