finally
Understand how to use the finally block for cleanup operations in Dart.
What is it?
The finally block is a section of code that always executes after a try-catch block, regardless of whether an exception was thrown or caught. It's used for cleanup operations like closing files, releasing resources, or resetting states.
Why does it exist?
finally exists to:
- Ensure cleanup code always runs
- Release resources (files, connections, etc.)
- Prevent resource leaks
- Reset state after operations
- Provide guaranteed execution
- Handle cleanup regardless of success/failure
Basic finally
Simple finally
The
finallyblock always executes, whether an exception occurs or not.
void main() {
print('Starting...');
try {
print('Inside try');
// No exception here
int result = 10 ~/ 2;
print('Result: $result');
} catch (e) {
print('Error caught: $e');
} finally {
print('Finally always executes');
}
print('After try-catch-finally');
}
// Output:
// Starting...
// Inside try
// Result: 5
// Finally always executes
// After try-catch-finally
What's happening here? -
tryblock executes successfully - No exception is thrown -finallyexecutes regardless - Program continues after the block
finally with Exception
When an exception occurs,
finallystill executes after thecatchblock.
void main() {
print('Starting...');
try {
print('Inside try');
int result = 10 ~/ 0; // Throws exception
print('This never executes');
} catch (e) {
print('Error caught: $e');
} finally {
print('Finally always executes even with error');
}
print('After try-catch-finally');
}
// Output:
// Starting...
// Inside try
// Error caught: IntegerDivisionByZeroException
// Finally always executes even with error
// After try-catch-finally
What's happening here? - Exception is thrown in
try- Execution jumps tocatch-catchhandles the exception -finallyexecutes aftercatch- Program continues normally
finally with Rethrow
finally with rethrow
When using
rethrow, thefinallyblock still executes before the exception propagates.
void riskyOperation() {
try {
print('Inside risky operation');
throw Exception('Something went wrong');
} catch (e) {
print('Caught in risky: $e');
// Add context
rethrow; // Propagate the exception
} finally {
print('Cleanup in risky operation');
}
}
void main() {
try {
riskyOperation();
} catch (e) {
print('Caught in main: $e');
}
}
// Output:
// Inside risky operation
// Caught in risky: Exception: Something went wrong
// Cleanup in risky operation
// Caught in main: Exception: Something went wrong
Key insights: -
finallyexecutes beforerethrow- Cleanup happens even when propagating - The exception still reaches the caller - This ensures proper cleanup
Real-World Examples
File Operations
finallyis essential for closing files and releasing resources.
import 'dart:io';
class FileProcessor {
void processFile(String path) {
File file = File(path);
RandomAccessFile? raf;
try {
print('Opening file: $path');
raf = file.openSync();
// Read and process file
var content = raf.readStringSync();
print('File content: $content');
// Could throw an exception
if (content.isEmpty) {
throw Exception('File is empty');
}
print('File processed successfully');
} catch (e) {
print('Error processing file: $e');
} finally {
// Always close the file
if (raf != null) {
print('Closing file');
raf.closeSync();
}
}
}
}
void main() {
var processor = FileProcessor();
// Try with existing file
try {
processor.processFile('data.txt');
} catch (e) {
print('Main caught: $e');
}
}
Database Connection
finallyensures database connections are always closed.
class DatabaseConnection {
bool _isConnected = false;
void connect() {
print('Connecting to database...');
_isConnected = true;
}
void query(String sql) {
if (!_isConnected) {
throw Exception('Not connected to database');
}
print('Executing: $sql');
if (sql.contains('error')) {
throw Exception('Query execution failed');
}
}
void disconnect() {
print('Disconnecting from database...');
_isConnected = false;
}
}
void performDatabaseOperations() {
var db = DatabaseConnection();
try {
db.connect();
db.query('SELECT * FROM users');
db.query('UPDATE users SET active = true');
// This will cause an error
db.query('DELETE FROM error');
} catch (e) {
print('Database error: $e');
} finally {
// Always disconnect
db.disconnect();
}
}
void main() {
print('Starting database operations...');
performDatabaseOperations();
print('Operations completed');
}
finally with return
finally and Return Statements
finallyexecutes even when there's areturnstatement intryorcatch.
String processData(String? input) {
try {
if (input == null) {
throw ArgumentError('Input cannot be null');
}
return 'Processed: $input';
} catch (e) {
print('Error: $e');
return 'Default value';
} finally {
print('Cleanup always runs');
}
}
void main() {
var result = processData('Hello');
print('Result: $result');
print('---');
var result2 = processData(null);
print('Result: $result2');
}
// Output:
// Cleanup always runs
// Result: Processed: Hello
// ---
// Error: ArgumentError: Input cannot be null
// Cleanup always runs
// Result: Default value
What's happening here? -
finallyexecutes beforereturn- This ensures cleanup happens - Even withreturn,finallyruns - Useful for resource cleanup
try-finally without catch
finally Alone
try-finallycan be used without acatchblock when you don't want to handle the exception locally.
void riskyOperation() {
try {
print('Performing risky operation');
throw Exception('Error occurred');
} finally {
print('Cleaning up resources');
}
}
void main() {
try {
riskyOperation();
} catch (e) {
print('Caught in main: $e');
}
}
// Output:
// Performing risky operation
// Cleaning up resources
// Caught in main: Exception: Error occurred
Key insights: -
finallyruns before exception propagates - The exception goes to the caller - Useful when you want cleanup but not handling - Combines with caller's error handling
Best Practices
Always Clean Up Resources
// Good: Resource cleanup in finally
void processFile() {
var file = File('data.txt');
var raf = file.openSync();
try {
// Process file
} finally {
raf.closeSync(); // Always closes
}
}
// Bad: Resource leak
void badProcessFile() {
var file = File('data.txt');
var raf = file.openSync();
// If exception occurs, file is never closed
}
Use finally with try-catch
// Good: Full error handling
void operation() {
try {
riskyOp();
} catch (e) {
handleError(e);
} finally {
cleanup();
}
}
// Bad: Missing finally
void badOperation() {
try {
riskyOp();
} catch (e) {
handleError(e);
}
cleanup(); // May not run if exception isn't caught
}
Common Mistakes
Modifying Return in finally
Wrong:
String getData() {
try {
return 'Success';
} finally {
return 'Finally'; // Overrides return value!
}
}
Correct:
String getData() {
try {
return 'Success';
} finally {
print('Cleanup'); // Don't modify return
}
}
Resource Leak
Wrong:
void process() {
var connection = getConnection();
try {
connection.send('data');
} catch (e) {
print('Error');
}
// connection never closed if exception occurs
}
Correct:
void process() {
var connection = getConnection();
try {
connection.send('data');
} catch (e) {
print('Error');
} finally {
connection.close(); // Always closes
}
}
Summary
finally guarantees that cleanup code runs regardless of exceptions. Use it for resource cleanup, state reset, and guaranteed execution.
Next Steps
Now that you understand finally, continue to:
Did You Know?
finallyalways executes, even withreturnfinallyruns beforerethrowfinallyworks withoutcatch- Use
finallyfor resource cleanup finallycannot modify return valuesfinallyexecutes aftertryorcatch- Always close resources in
finally