A demonstration of using the RICOH THETA X camera with flutter
12. Take Save Display
A demonstration of using the RICOH THETA X camera with Android and IOS phones.
Features
- Take a picture
- Save image to gallery
- Select image from gallery & display
- View image in 360 view
Resources
This application is built on the tutorials below.
- THETA Concept 3 (taking picture with Bloc management)
- THETA Concept 6 (checking camera state before moving on)
- THETA Concept 9 (saving image to gallery)
- THETA Concept 11 (selecting image from gallery)
Key Flutter Packages
- panorama – view 360 image with navigation
- image_picker – select image from gallery
- gallery_saver – save image to gallery
- flutter_bloc – manage state
View Image in 360
This project uses the panorama package to view the image in 360 view. When the user clicks on the image, the Navigator.push
displays it in full screen.
class PanoramaScreen extends StatefulWidget {
File myFile;
PanoramaScreen({Key? key, required this.myFile}) : super(key: key);
@override
State<PanoramaScreen> createState() => _PanoramaScreenState();
}
class _PanoramaScreenState extends State<PanoramaScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Panorama(child: Image.file(widget.myFile)),
));
}
}
Taking Picture
Refer to Tutorial 6. Multiple RICOH THETA API Commands in Sequence for more description of the process.
The project executes the command camera.takePicture
from the RICOH THETA API. It continously checks to see if the takePicture process is done
with commands/status
. When the image is finished processing, the project moves on to the next step: saving the image to the gallery.
on<PictureEvent>((event, emit) async {
var url = Uri.parse('http://192.168.1.1/osc/commands/execute');
var header = {'Content-Type': 'application/json;charset=utf-8'};
var bodyMap = {'name': 'camera.takePicture'};
var bodyJson = jsonEncode(bodyMap);
var response = await http.post(url, headers: header, body: bodyJson);
var convertResponse = jsonDecode(response.body);
var id = convertResponse['id'];
if (convertResponse != null && id != null) {
emit(ThetaState(message: response.body, id: id));
while (state.cameraState != "done") {
add(CameraStatusEvent());
await Future.delayed(Duration(milliseconds: 200));
print(state.cameraState);
}
}
add(GetFileEvent());
});
Saving Image to Gallery
The GetFileEvent
retrieves the last file url from the camera with camera.listFiles
. It parses out the url from the response and updates the State with the file url.
on<GetFileEvent>((event, emit) async {
...
var fileUrl = convertResponse['results']['entries'][0]['fileUrl'];
emit(state.copyWith(fileUrl: fileUrl));
...
}
Import the gallery_saver package to the project and add permission in the AndroidManifest.xml
file.
android.permission.WRITE_EXTERNAL_STORAGE
Save the image with GallerySaver.saveImage
inside of the SaveFileEvent
and notify the State that the image is finished saving.
on<SaveFileEvent>((event, emit) async {
await GallerySaver.saveImage(state.fileUrl).then((value) {
emit(state.copyWith(finishedSaving: true));
});
});
Selecting Image from Gallery
This section of the application follows the tutorial by Learn Flutter with Smirty.
theta_event.dart
...
class ImagePickerEvent extends ThetaEvent {
final XFile image;
ImagePickerEvent(this.image);
}
theta_bloc.dart
on<ImagePickerEvent>((event, emit) async {
emit(state.copyWith(images: event.image));
});
main.dart
IconButton(
onPressed: () async {
final image = await ImagePicker().pickImage(
source: ImageSource.gallery,
);
if (image == null) return;
context
.read<ThetaBloc>()
.add(ImagePickerEvent(image));
},
icon: Icon(Icons.image)),
When the IconButton
is pressed, it adds the ImagePickerEvent
with the file from ImagePicker
. Inside the Bloc file, the ImagePickerEvent
updates the State with the file.
Bloc Structure
This project uses the flutter_bloc package to handle State management. Events are associated with every action that occurs. The State holds information in parameters and the main constructor. In the Bloc file, there are on
methods that handle when every Event is called.
Example of the State constructor:
class ThetaState extends Equatable {
final String message;
final String fileUrl;
final String cameraState;
final String id;
final bool finishedSaving;
final XFile? images;
ThetaState(
{required this.message,
this.fileUrl = "",
this.cameraState = "initial",
this.id = "",
this.finishedSaving = false,
this.images});}