12. Take Save Display

A demonstration of using the RICOH THETA X camera with Android and IOS phones.


  • Take a picture
  • Save image to gallery
  • Select image from gallery & display
  • View image in 360 view



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);

  State<PanoramaScreen> createState() => _PanoramaScreenState();

class _PanoramaScreenState extends State<PanoramaScreen> {
  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('');
      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") {
          await Future.delayed(Duration(milliseconds: 200));

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.


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.


class ImagePickerEvent extends ThetaEvent {
  final XFile image;



 on<ImagePickerEvent>((event, emit) async {
      emit(state.copyWith(images: event.image));


    onPressed: () async {
        final image = await ImagePicker().pickImage(
        source: ImageSource.gallery,
        if (image == null) return;
    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;

      {required this.message,
      this.fileUrl = "",
      this.cameraState = "initial",
      this.id = "",
      this.finishedSaving = false,


View Github